mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
Updates complex decimation filters to use either scalar or vector optimal implementation. (#1148)
Updates HackRF native byte buffers with automatic DC removal. Creates new half-band channel source to replace the cic-based channel source in the heterodyne channelizer. Resolves issue in RTL native buffer processing. Creates a CACHED thread pool and reduces the number of threads for the SCHEDULED thread pool to 2. Changes each of the channel source implementations to use a dedicated thread (Dispatcher class) instead of the scheduled thread pool, to reduce the amount of buffered/retained samples objects in memory. Lots of other small changes and tweaks. Removes last of Reusable buffer code. Updates DFTProcessor to use a single thread scheduled executor. Updates Polyphase channelizer to use threads instead of an executor. Removes a number of unused classes. Commenting out window calibration. Scalar is faster in both implementations. Adds vector and scalar implementations for windows. Repackages window and window type classes. WIP adds vector implementations for Airspy non-interleaved buffer iterator and calibration plugins for both interleaved and non-interleaved buffer iterators. WIP adds vector implementations for Airspy interleaved buffer iterator. Work on developing scalar and vector implementations of airspy sample conversion and airspy native buffer processing. Introduces new INativeBuffer framework where tuner raw samples are retained in their native (compressed) format until when they're needed and they are converted on-demand into smaller complex sample buffers to aid garbage collection. Removes all usage of ReusableComplexBuffers and ReusableChannelREsultsBuffers. Updates polyphase channelizer to be more efficient in output channel processing. Updates ViterbiDecoder to change Map usage to array for memory efficiency. Updates channel processors to used scalar implementation for gain and mixer, until the polyphase channel results can be fixed to be a multiple of the SIMD lane width. Resolves final changes for the conversion from interleaved sample arrays to complexsamples buffers for distributing baseband samples to channels and processing chains. Updates tuner channel sources and all decoders to receive new complex samples buffers, removing the previous reusable complex buffers as the default baseband samples buffer object. Still to resolve is the ComplexMixer implementations. Needs validation and a calibration implementation with an update to the complex mixer factory. Updates the DMR decoder to use optimal (scalar vs vector) implementations of filter and AGC controls. Updates the AM demodulator for separated I&Q float sample processing. Code review and testing changes. updates build script for runtime configuration. Integrates calibration manager with main sdrtrunk application. Updates NBFMDecoder to use new optimized FIR and Decimation filter options. Updates real half-band decimation filters to select the optimal (scalar vs vector) implementation. Adds calibration manager plugins. Adds calibration manager and plugins to determine the optimal (scalar vs vector) implementation type to use for various classes of dsp components. Adds vector squelching FM demodulator and cleans up the IIR filter and power squelch to use all floats. Adds vector implementation of FM demodulator. This introduces a new interface that accepts i/q samples as two separate float arrays, versus an interleaved array, which enables more efficient processing. This new complex sample interface will be used across the codebase eventually. Adds ComplexOscillator and a SIMD version VectorComplexOscillator. Deprecates the LowPhaseNoiseOscillator because the ComplexOscillator is about 35% faster. The vectorized version is about 43% faster when SIMD lane width is 8x. Adds new vector complex filters and new real oscillators. Note: the RealOscillator using straight Java math sin() function is 3x slower than the existing legacy implementation. Will replace this with a complex oscillator that only uses the real part. Updates ComplexFIRFilter to be 50% more efficient. Adds 23 tap filter implementations. Will not add 63-tap implementations because they are slower than the default vector implementation. Adds 15-tap customized vector half band filters. Adds initial custom complex vector half-band decimation filters for 11-tap and 15-tap decimation filters.. Adds vector complex half band filter implementations. Note: performance of the generic 64-bit 2-lane filter is horrible and is not included. Optimizes complex half-band filter to be 27% more efficient. Implements vectorized real half-band filter implementations. Implements a new FIR filter class that processes sample buffers instead of single sample at a time. Refactored all of the calibratons to be time-duration based. Testing on windows i7 gen 3 CPU showed that it took 1000x longer for one of the calibrations to run, versus my AMD 5800x. Changed gradle jvm args so that the windows plaf export is always applied. Updates gradle build script to ensure incubator module is included and to cleanup javafx library management for building runtime images. Testing tweaks and changes. Adds logging limiter to dispatcher for buffer overflows Adds User Preferences editor for performing Vector/SIMD calibrations and new startup dialog prompting the user to perform calibrations. Updates calibration manager and all calibration implementations to add warm-up and test phases and to reduce the overall time it takes to calibrate a system. Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
@@ -1,518 +0,0 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 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 io.github.dsheirer.spectrum;
|
||||
|
||||
import io.github.dsheirer.controller.channel.Channel;
|
||||
import io.github.dsheirer.controller.channel.ChannelEvent;
|
||||
import io.github.dsheirer.controller.channel.ChannelEventListener;
|
||||
import io.github.dsheirer.controller.channel.ChannelProcessingManager;
|
||||
import io.github.dsheirer.dsp.filter.Filters;
|
||||
import io.github.dsheirer.dsp.filter.Window.WindowType;
|
||||
import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter;
|
||||
import io.github.dsheirer.dsp.filter.smoothing.SmoothingFilter.SmoothingType;
|
||||
import io.github.dsheirer.module.ProcessingChain;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.SampleType;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import io.github.dsheirer.settings.ColorSetting.ColorSettingName;
|
||||
import io.github.dsheirer.settings.ColorSettingMenuItem;
|
||||
import io.github.dsheirer.settings.Setting;
|
||||
import io.github.dsheirer.settings.SettingChangeListener;
|
||||
import io.github.dsheirer.settings.SettingsManager;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.spectrum.converter.DFTResultsConverter;
|
||||
import io.github.dsheirer.spectrum.converter.RealDecibelConverter;
|
||||
import io.github.dsheirer.spectrum.menu.AveragingItem;
|
||||
import io.github.dsheirer.spectrum.menu.DFTSizeItem;
|
||||
import io.github.dsheirer.spectrum.menu.FFTWindowTypeItem;
|
||||
import io.github.dsheirer.spectrum.menu.FrameRateItem;
|
||||
import io.github.dsheirer.spectrum.menu.SmoothingItem;
|
||||
import io.github.dsheirer.spectrum.menu.SmoothingTypeItem;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.JLayeredPane;
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JSeparator;
|
||||
import javax.swing.SwingUtilities;
|
||||
import java.awt.Component;
|
||||
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.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class ChannelSpectrumPanel extends JPanel implements ChannelEventListener,
|
||||
Listener<ReusableFloatBuffer>, SettingChangeListener, SpectralDisplayAdjuster
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.REAL);
|
||||
private DFTResultsConverter mDFTConverter = new RealDecibelConverter();
|
||||
private JLayeredPane mLayeredPane;
|
||||
private SpectrumPanel mSpectrumPanel;
|
||||
private ChannelOverlayPanel mOverlayPanel;
|
||||
private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("ChannelSpectrumPanel");
|
||||
|
||||
private Channel mCurrentChannel;
|
||||
|
||||
private int mSampleBufferSize = 2400;
|
||||
|
||||
private HalfBandFilter mDecimatingFilter = new HalfBandFilter(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 1.0f, true);
|
||||
|
||||
private AtomicBoolean mEnabled = new AtomicBoolean();
|
||||
|
||||
private SettingsManager mSettingsManager;
|
||||
private ChannelProcessingManager mChannelProcessingManager;
|
||||
|
||||
public ChannelSpectrumPanel(SettingsManager settingsManager,
|
||||
ChannelProcessingManager channelProcessingManager)
|
||||
{
|
||||
mSettingsManager = settingsManager;
|
||||
mChannelProcessingManager = channelProcessingManager;
|
||||
|
||||
if(mSettingsManager != null)
|
||||
{
|
||||
mSettingsManager.addListener(this);
|
||||
}
|
||||
|
||||
mSpectrumPanel = new SpectrumPanel(mSettingsManager);
|
||||
mSpectrumPanel.setAveraging(1);
|
||||
|
||||
mOverlayPanel = new ChannelOverlayPanel(mSettingsManager);
|
||||
mDFTProcessor.addConverter(mDFTConverter);
|
||||
mDFTConverter.addListener(mSpectrumPanel);
|
||||
|
||||
/* Set the DFTProcessor to the decimated 24kHz sample rate */
|
||||
mDFTProcessor.process(SourceEvent.sampleRateChange(24000.0));
|
||||
|
||||
initGui();
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
setEnabled(false);
|
||||
|
||||
mDFTProcessor.dispose();
|
||||
|
||||
if(mSettingsManager != null)
|
||||
{
|
||||
mSettingsManager.removeListener(this);
|
||||
}
|
||||
|
||||
mSettingsManager = null;
|
||||
mCurrentChannel = null;
|
||||
mDFTProcessor = null;
|
||||
mSpectrumPanel = null;
|
||||
}
|
||||
|
||||
public void setFrameRate(int framesPerSecond)
|
||||
{
|
||||
mSampleBufferSize = (int)(48000 / framesPerSecond);
|
||||
|
||||
mDFTProcessor.setFrameRate(framesPerSecond);
|
||||
}
|
||||
|
||||
private void initGui()
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0 ",
|
||||
"[grow,fill]",
|
||||
"[grow,fill]"));
|
||||
|
||||
mLayeredPane = new JLayeredPane();
|
||||
mLayeredPane.addComponentListener(new ResizeListener());
|
||||
|
||||
MouseEventProcessor mouser = new MouseEventProcessor();
|
||||
|
||||
mOverlayPanel.addMouseListener(mouser);
|
||||
mOverlayPanel.addMouseMotionListener(mouser);
|
||||
|
||||
mLayeredPane.add(mSpectrumPanel, 0, 0);
|
||||
mLayeredPane.add(mOverlayPanel, 1, 0);
|
||||
|
||||
add(mLayeredPane);
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled)
|
||||
{
|
||||
if(enabled && mEnabled.compareAndSet(false, true))
|
||||
{
|
||||
start();
|
||||
}
|
||||
else if(!enabled && mEnabled.compareAndSet(true, false))
|
||||
{
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelChanged(ChannelEvent event)
|
||||
{
|
||||
switch(event.getEvent())
|
||||
{
|
||||
case NOTIFICATION_SELECTION_CHANGE:
|
||||
//ChannelSelectionManager ensures that only 1 channel can be
|
||||
//selected and any previously selected channel will be first
|
||||
//deselected before we get a new selection event
|
||||
if(event.getChannel().isSelected())
|
||||
{
|
||||
if(mCurrentChannel != null)
|
||||
{
|
||||
stop();
|
||||
mCurrentChannel = null;
|
||||
}
|
||||
|
||||
mCurrentChannel = event.getChannel();
|
||||
|
||||
if(mEnabled.get())
|
||||
{
|
||||
start();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stop();
|
||||
mCurrentChannel = null;
|
||||
}
|
||||
break;
|
||||
case NOTIFICATION_PROCESSING_STOP:
|
||||
if(event.getChannel() == mCurrentChannel)
|
||||
{
|
||||
if(mEnabled.get())
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
mCurrentChannel = null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void start()
|
||||
{
|
||||
if(mEnabled.get() && mCurrentChannel != null && mCurrentChannel.isProcessing())
|
||||
{
|
||||
ProcessingChain processingChain = mChannelProcessingManager
|
||||
.getProcessingChain(mCurrentChannel);
|
||||
|
||||
if(processingChain != null)
|
||||
{
|
||||
processingChain.addDemodulatedAudioListener(this);
|
||||
mDFTProcessor.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void stop()
|
||||
{
|
||||
if(mCurrentChannel != null && mCurrentChannel.isProcessing())
|
||||
{
|
||||
ProcessingChain processingChain = mChannelProcessingManager.getProcessingChain(mCurrentChannel);
|
||||
|
||||
if(processingChain != null)
|
||||
{
|
||||
processingChain.removeDemodulatedAudioListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
mDFTProcessor.stop();
|
||||
|
||||
mSpectrumPanel.clearSpectrum();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void settingChanged(Setting setting)
|
||||
{
|
||||
if(mSpectrumPanel != null)
|
||||
{
|
||||
mSpectrumPanel.settingChanged(setting);
|
||||
}
|
||||
if(mOverlayPanel != null)
|
||||
{
|
||||
mOverlayPanel.settingChanged(setting);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void settingDeleted(Setting setting)
|
||||
{
|
||||
if(mSpectrumPanel != null)
|
||||
{
|
||||
mSpectrumPanel.settingDeleted(setting);
|
||||
}
|
||||
|
||||
if(mOverlayPanel != null)
|
||||
{
|
||||
mOverlayPanel.settingDeleted(setting);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ReusableFloatBuffer buffer)
|
||||
{
|
||||
ReusableFloatBuffer decimated = mDecimatingFilter.filter(buffer);
|
||||
|
||||
//Hack: we're placing real samples in a complex buffer that the DFT
|
||||
//processor is expecting.
|
||||
ReusableComplexBuffer reusableComplexBuffer = mReusableComplexBufferQueue.getBuffer(decimated.getSamples().length);
|
||||
reusableComplexBuffer.reloadFrom(decimated.getSamples(), System.currentTimeMillis());
|
||||
mDFTProcessor.receive(reusableComplexBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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());
|
||||
mOverlayPanel.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() == mOverlayPanel)
|
||||
{
|
||||
mOverlayPanel.setCursorLocation(event.getPoint());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e)
|
||||
{
|
||||
if(e.getComponent() == mOverlayPanel)
|
||||
{
|
||||
mOverlayPanel.setCursorVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e)
|
||||
{
|
||||
mOverlayPanel.setCursorVisible(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the context menu.
|
||||
*/
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent event)
|
||||
{
|
||||
if(SwingUtilities.isRightMouseButton(event))
|
||||
{
|
||||
JPopupMenu contextMenu = new JPopupMenu();
|
||||
|
||||
/**
|
||||
* Color Menus
|
||||
*/
|
||||
JMenu colorMenu = new JMenu("Color");
|
||||
|
||||
colorMenu.add(new ColorSettingMenuItem(mSettingsManager,
|
||||
ColorSettingName.SPECTRUM_CURSOR));
|
||||
|
||||
colorMenu.add(new ColorSettingMenuItem(mSettingsManager,
|
||||
ColorSettingName.SPECTRUM_LINE));
|
||||
|
||||
colorMenu.add(new ColorSettingMenuItem(mSettingsManager,
|
||||
ColorSettingName.SPECTRUM_BACKGROUND));
|
||||
|
||||
colorMenu.add(new ColorSettingMenuItem(mSettingsManager,
|
||||
ColorSettingName.SPECTRUM_GRADIENT_BOTTOM));
|
||||
|
||||
colorMenu.add(new ColorSettingMenuItem(mSettingsManager,
|
||||
ColorSettingName.SPECTRUM_GRADIENT_TOP));
|
||||
|
||||
contextMenu.add(colorMenu);
|
||||
|
||||
/**
|
||||
* Display items: fft and frame rate
|
||||
*/
|
||||
JMenu displayMenu = new JMenu("Display");
|
||||
contextMenu.add(displayMenu);
|
||||
|
||||
/**
|
||||
* Averaging menu
|
||||
*/
|
||||
JMenu averagingMenu = new JMenu("Averaging");
|
||||
averagingMenu.add(
|
||||
new AveragingItem(ChannelSpectrumPanel.this, 2));
|
||||
displayMenu.add(averagingMenu);
|
||||
|
||||
/**
|
||||
* FFT width
|
||||
*/
|
||||
JMenu fftWidthMenu = new JMenu("FFT Width");
|
||||
displayMenu.add(fftWidthMenu);
|
||||
|
||||
for(DFTSize width : DFTSize.values())
|
||||
{
|
||||
fftWidthMenu.add(new DFTSizeItem(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));
|
||||
}
|
||||
|
||||
/**
|
||||
* Smoothing menu
|
||||
*/
|
||||
JMenu smoothingMenu = new JMenu("Smoothing");
|
||||
|
||||
if(mSpectrumPanel.getSmoothingType() != SmoothingType.NONE)
|
||||
{
|
||||
smoothingMenu.add(new SmoothingItem(ChannelSpectrumPanel.this, 5));
|
||||
smoothingMenu.add(new JSeparator());
|
||||
}
|
||||
|
||||
smoothingMenu.add(new SmoothingTypeItem(ChannelSpectrumPanel.this, SmoothingType.GAUSSIAN));
|
||||
smoothingMenu.add(new SmoothingTypeItem(ChannelSpectrumPanel.this, SmoothingType.TRIANGLE));
|
||||
smoothingMenu.add(new SmoothingTypeItem(ChannelSpectrumPanel.this, SmoothingType.RECTANGLE));
|
||||
smoothingMenu.add(new SmoothingTypeItem(ChannelSpectrumPanel.this, SmoothingType.NONE));
|
||||
|
||||
displayMenu.add(smoothingMenu);
|
||||
|
||||
if(contextMenu != null)
|
||||
{
|
||||
contextMenu.show(mOverlayPanel,
|
||||
event.getX(),
|
||||
event.getY());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAveraging()
|
||||
{
|
||||
return mSpectrumPanel.getAveraging();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAveraging(int averaging)
|
||||
{
|
||||
mSpectrumPanel.setAveraging(averaging);
|
||||
}
|
||||
|
||||
public void setSampleSize(double sampleSize)
|
||||
{
|
||||
mSpectrumPanel.setSampleSize(sampleSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSmoothing()
|
||||
{
|
||||
return mSpectrumPanel.getSmoothing();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSmoothing(int smoothing)
|
||||
{
|
||||
mSpectrumPanel.setSmoothing(smoothing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SmoothingType getSmoothingType()
|
||||
{
|
||||
return mSpectrumPanel.getSmoothingType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSmoothingType(SmoothingType type)
|
||||
{
|
||||
mSpectrumPanel.setSmoothingType(type);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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
|
||||
@@ -15,28 +14,25 @@
|
||||
*
|
||||
* 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 io.github.dsheirer.spectrum;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.Window;
|
||||
import io.github.dsheirer.dsp.filter.Window.WindowType;
|
||||
import io.github.dsheirer.buffer.INativeBuffer;
|
||||
import io.github.dsheirer.controller.NamingThreadFactory;
|
||||
import io.github.dsheirer.dsp.window.WindowFactory;
|
||||
import io.github.dsheirer.dsp.window.WindowType;
|
||||
import io.github.dsheirer.properties.SystemProperties;
|
||||
import io.github.dsheirer.sample.IOverflowListener;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.SampleType;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.source.ISourceEventProcessor;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.spectrum.converter.DFTResultsConverter;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.jtransforms.fft.FloatFFT_1D;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -45,37 +41,29 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
* 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<ReusableComplexBuffer>, ISourceEventProcessor, IDFTWidthChangeProcessor
|
||||
public class ComplexDftProcessor<T extends INativeBuffer> implements Listener<T>, IDFTWidthChangeProcessor
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(DFTProcessor.class);
|
||||
private static final int BUFFER_QUEUE_MAX_SIZE = 20;
|
||||
private static final int BUFFER_QUEUE_OVERFLOW_RESET_THRESHOLD = 6;
|
||||
private static final Logger mLog = LoggerFactory.getLogger(ComplexDftProcessor.class);
|
||||
private static final String FRAME_RATE_PROPERTY = "spectral.display.frame.rate";
|
||||
|
||||
//The Cosine and Hann windows seem to offer the best spectral display with minimal bin leakage/smearing
|
||||
private WindowType mWindowType = WindowType.HANN;
|
||||
private double[] mWindow;
|
||||
private float[] mWindow;
|
||||
private DFTSize mDFTSize = DFTSize.FFT04096;
|
||||
private DFTSize mNewDFTSize = DFTSize.FFT04096;
|
||||
private FloatFFT_1D mFFT = new FloatFFT_1D(mDFTSize.getSize());
|
||||
private int mFrameRate;
|
||||
private int mSampleRate = 2400000; //Initial high value until we receive update from tuner
|
||||
private int mFrameSize;
|
||||
private int mFrameFlushCount;
|
||||
private int mFrameOverlapCount;
|
||||
private SampleType mSampleType;
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
private ScheduledFuture<?> mProcessorTaskHandle;
|
||||
private CopyOnWriteArrayList<DFTResultsConverter> mListeners = new CopyOnWriteArrayList<DFTResultsConverter>();
|
||||
private OverflowableBufferStream mOverflowableBufferStream = new OverflowableBufferStream(BUFFER_QUEUE_MAX_SIZE,
|
||||
BUFFER_QUEUE_OVERFLOW_RESET_THRESHOLD, mDFTSize.getSize());
|
||||
private float[] mPreviousSamples;
|
||||
private ScheduledExecutorService mExecutorService = Executors.newSingleThreadScheduledExecutor(new NamingThreadFactory("sdrtrunk dft processor"));
|
||||
private CopyOnWriteArrayList<DFTResultsConverter> mListeners = new CopyOnWriteArrayList<>();
|
||||
private NativeBufferManager mDftBufferManager = new NativeBufferManager(mDFTSize.getSize() * 2);
|
||||
private float[] mPreviousSamples = new float[mDFTSize.getSize() * 2];
|
||||
|
||||
public DFTProcessor(SampleType sampleType)
|
||||
public ComplexDftProcessor()
|
||||
{
|
||||
setSampleType(sampleType);
|
||||
mFrameRate = SystemProperties.getInstance().get(FRAME_RATE_PROPERTY, 20);
|
||||
calculateConsumptionRate();
|
||||
setWindowType(mWindowType);
|
||||
start();
|
||||
}
|
||||
|
||||
@@ -84,19 +72,9 @@ public class DFTProcessor implements Listener<ReusableComplexBuffer>, ISourceEve
|
||||
stop();
|
||||
|
||||
mListeners.clear();
|
||||
mOverflowableBufferStream.clear();
|
||||
mWindow = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the listener to receive buffer overflow/reset indications
|
||||
* @param listener
|
||||
*/
|
||||
public void setOverflowListener(IOverflowListener listener)
|
||||
{
|
||||
mOverflowableBufferStream.setOverflowListener(listener);
|
||||
}
|
||||
|
||||
public WindowType getWindowType()
|
||||
{
|
||||
return mWindowType;
|
||||
@@ -105,30 +83,12 @@ public class DFTProcessor implements Listener<ReusableComplexBuffer>, ISourceEve
|
||||
public void setWindowType(WindowType windowType)
|
||||
{
|
||||
mWindowType = windowType;
|
||||
|
||||
if(mSampleType == SampleType.COMPLEX)
|
||||
{
|
||||
mWindow = Window.getWindow(mWindowType, mDFTSize.getSize() * 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
mWindow = Window.getWindow(mWindowType, mDFTSize.getSize());
|
||||
}
|
||||
updateWindow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the processor mode to Float or Complex, depending on the sample
|
||||
* types that will be delivered for processing
|
||||
*/
|
||||
public void setSampleType(SampleType type)
|
||||
private void updateWindow()
|
||||
{
|
||||
mSampleType = type;
|
||||
setWindowType(mWindowType);
|
||||
}
|
||||
|
||||
public SampleType getSampleType()
|
||||
{
|
||||
return mSampleType;
|
||||
mWindow = WindowFactory.getWindow(mWindowType, mDFTSize.getSize() * 2);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,11 +119,7 @@ public class DFTProcessor implements Listener<ReusableComplexBuffer>, ISourceEve
|
||||
}
|
||||
|
||||
mFrameRate = framesPerSecond;
|
||||
|
||||
SystemProperties.getInstance().set(FRAME_RATE_PROPERTY, mFrameRate);
|
||||
|
||||
calculateConsumptionRate();
|
||||
|
||||
restart();
|
||||
}
|
||||
|
||||
@@ -175,7 +131,7 @@ public class DFTProcessor implements Listener<ReusableComplexBuffer>, ISourceEve
|
||||
int initialDelay = 0;
|
||||
int period = (int) (1000 / mFrameRate);
|
||||
|
||||
mProcessorTaskHandle = ThreadPool.SCHEDULED.scheduleAtFixedRate(new DFTCalculationTask(), initialDelay, period,
|
||||
mProcessorTaskHandle = mExecutorService.scheduleAtFixedRate(new DFTCalculationTask(), initialDelay, period,
|
||||
TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
@@ -205,9 +161,9 @@ public class DFTProcessor implements Listener<ReusableComplexBuffer>, ISourceEve
|
||||
* Places the sample into a transfer queue for future processing.
|
||||
*/
|
||||
@Override
|
||||
public void receive(ReusableComplexBuffer sampleBuffer)
|
||||
public void receive(T buffer)
|
||||
{
|
||||
mOverflowableBufferStream.offer(sampleBuffer);
|
||||
mDftBufferManager.add(buffer);
|
||||
}
|
||||
|
||||
private void calculate()
|
||||
@@ -219,31 +175,15 @@ public class DFTProcessor implements Listener<ReusableComplexBuffer>, ISourceEve
|
||||
|
||||
try
|
||||
{
|
||||
if(mFrameFlushCount > 0)
|
||||
{
|
||||
mOverflowableBufferStream.flush(mFrameFlushCount);
|
||||
}
|
||||
|
||||
//If this throws an IO exception, the buffer queue is (temporarily) empty and we return from the method
|
||||
float[] samples = mOverflowableBufferStream.get(mFrameSize, mFrameOverlapCount);
|
||||
|
||||
Window.apply(mWindow, samples);
|
||||
|
||||
if(mSampleType == SampleType.REAL)
|
||||
{
|
||||
mFFT.realForward(samples);
|
||||
}
|
||||
else
|
||||
{
|
||||
mFFT.complexForward(samples);
|
||||
}
|
||||
|
||||
float[] samples = mDftBufferManager.get(mDFTSize.getSize());
|
||||
WindowFactory.apply(mWindow, samples);
|
||||
mFFT.complexForward(samples);
|
||||
mPreviousSamples = samples;
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
//No new data, dispatch the previous samples again
|
||||
//no-op
|
||||
//Not enough samples available, dispatch the previous samples again
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@@ -264,8 +204,8 @@ public class DFTProcessor implements Listener<ReusableComplexBuffer>, ISourceEve
|
||||
*/
|
||||
private void dispatch(float[] results)
|
||||
{
|
||||
|
||||
for (DFTResultsConverter mListener : mListeners) {
|
||||
for (DFTResultsConverter mListener : mListeners)
|
||||
{
|
||||
mListener.receive(results);
|
||||
}
|
||||
}
|
||||
@@ -286,9 +226,7 @@ public class DFTProcessor implements Listener<ReusableComplexBuffer>, ISourceEve
|
||||
if(mRunning.compareAndSet(false, true))
|
||||
{
|
||||
checkFFTSize();
|
||||
|
||||
calculate();
|
||||
|
||||
mRunning.set(false);
|
||||
}
|
||||
}
|
||||
@@ -309,98 +247,13 @@ public class DFTProcessor implements Listener<ReusableComplexBuffer>, ISourceEve
|
||||
if(mNewDFTSize.getSize() != mDFTSize.getSize())
|
||||
{
|
||||
mDFTSize = mNewDFTSize;
|
||||
|
||||
calculateConsumptionRate();
|
||||
|
||||
setWindowType(mWindowType);
|
||||
|
||||
updateWindow();
|
||||
mFFT = new FloatFFT_1D(mDFTSize.getSize());
|
||||
}
|
||||
}
|
||||
|
||||
public void clearBuffer()
|
||||
{
|
||||
mOverflowableBufferStream.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(SourceEvent event)
|
||||
{
|
||||
switch(event.getEvent())
|
||||
{
|
||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||
mSampleRate = event.getValue().intValue();
|
||||
calculateConsumptionRate();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the frame size, flush count and overlap count to use for each calculation cycle.
|
||||
*/
|
||||
private void calculateConsumptionRate()
|
||||
{
|
||||
int floatsPerSample = mSampleType == SampleType.COMPLEX ? 2 : 1;
|
||||
|
||||
mFrameSize = mDFTSize.getSize() * floatsPerSample;
|
||||
mPreviousSamples = new float[mFrameSize];
|
||||
|
||||
int productionRate = mSampleRate * floatsPerSample;
|
||||
int consumptionRate = mFrameRate * mFrameSize;
|
||||
int residual = productionRate - consumptionRate;
|
||||
|
||||
if(residual < 0)
|
||||
{
|
||||
mFrameFlushCount = 0;
|
||||
mFrameOverlapCount = -residual / mFrameRate;
|
||||
|
||||
if(mFrameOverlapCount * mFrameRate < -residual)
|
||||
{
|
||||
mFrameOverlapCount ++;
|
||||
}
|
||||
}
|
||||
else if(residual > 0)
|
||||
{
|
||||
mFrameOverlapCount = 0;
|
||||
mFrameFlushCount = residual / mFrameRate;
|
||||
|
||||
if(mFrameFlushCount * mFrameRate < residual)
|
||||
{
|
||||
mFrameFlushCount++;
|
||||
}
|
||||
}
|
||||
|
||||
//Ensure we're flushing or overlapping by even multiples of the sample size
|
||||
if(mFrameOverlapCount % floatsPerSample != 0)
|
||||
{
|
||||
mFrameOverlapCount++;
|
||||
}
|
||||
|
||||
if(mFrameFlushCount % floatsPerSample != 0)
|
||||
{
|
||||
mFrameFlushCount++;
|
||||
}
|
||||
|
||||
//If the overlap size is greater than the frame size, we can't do that. Automatically decrease the frame rate
|
||||
//until we reach a legitimate overlap value.
|
||||
if(mFrameOverlapCount >= mFrameSize)
|
||||
{
|
||||
mLog.warn("Unable to provide frame rate [" + mFrameRate + "] for current DFT size [" + mDFTSize.getSize() +
|
||||
"] - reducing frame rate");
|
||||
|
||||
mFrameRate--;
|
||||
|
||||
if(mFrameRate <= 0)
|
||||
{
|
||||
//Consider reducing the FFT size as a possible alternative corrective measure
|
||||
throw new IllegalStateException("Unable to determine a viable frame rate based on incoming sample " +
|
||||
"rate, DFT size and DFT frame rate");
|
||||
}
|
||||
|
||||
//Recursively call this method to test the new frame rate
|
||||
calculateConsumptionRate();
|
||||
}
|
||||
mDftBufferManager.clear();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,26 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 io.github.dsheirer.spectrum;
|
||||
|
||||
public interface IDFTWidthChangeProcessor
|
||||
{
|
||||
public void setDFTSize( DFTSize size );
|
||||
public DFTSize getDFTSize();
|
||||
void setDFTSize( DFTSize size );
|
||||
DFTSize getDFTSize();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 io.github.dsheirer.spectrum;
|
||||
|
||||
import io.github.dsheirer.buffer.INativeBuffer;
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
|
||||
/**
|
||||
* Thread-safe native buffer manager. Receives incoming native buffers in a non-blocking manner. Received buffers are
|
||||
* only enqueued if they are needed to meet the next requested sample array size. Once the incoming producer quota
|
||||
* is reached, incoming buffers are ignored.
|
||||
*
|
||||
* Transferred native buffers are enqueued on the consumer side until enough samples are amassed to satisfy a get
|
||||
* operation.
|
||||
*
|
||||
* The queuing on both the producer and consumer side ensures the thread-safe transfer queue never overfills.
|
||||
*
|
||||
* The estimated request size argument sets the initial quota of samples to enqueue on the producer size. This value
|
||||
* is updated on the consumer side with each request. This class is designed for the get() operation to consistently
|
||||
* request the same size buffer, with occasional changes.
|
||||
*
|
||||
* @param <T> native buffer
|
||||
*/
|
||||
public class NativeBufferManager<T extends INativeBuffer>
|
||||
{
|
||||
private LinkedTransferQueue<T> mTransferQueue = new LinkedTransferQueue<>();
|
||||
private List<T> mProducerQueue = new ArrayList<>();
|
||||
private List<T> mConsumerQueue = new ArrayList<>();
|
||||
private int mRequestSize;
|
||||
private int mProducerAvailable;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param estimatedRequestSize initially, for each complex sample retrieval.
|
||||
*/
|
||||
public NativeBufferManager(int estimatedRequestSize)
|
||||
{
|
||||
mRequestSize = estimatedRequestSize;
|
||||
}
|
||||
/**
|
||||
* Adds the buffer to this manager if it's needed.
|
||||
* @param nativeBuffer to add
|
||||
*/
|
||||
public void add(T nativeBuffer)
|
||||
{
|
||||
//If we have enough produced buffers and the transfer queue is empty ... move them to the queue
|
||||
if(mProducerAvailable >= mRequestSize && mTransferQueue.isEmpty())
|
||||
{
|
||||
mTransferQueue.addAll(mProducerQueue);
|
||||
mProducerQueue.clear();
|
||||
mProducerAvailable = 0;
|
||||
}
|
||||
|
||||
//Add this buffer to the producer queue if it's needed to meet the anticipated request size
|
||||
if(mProducerAvailable < mRequestSize)
|
||||
{
|
||||
mProducerQueue.add(nativeBuffer);
|
||||
mProducerAvailable += nativeBuffer.sampleCount();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears/removes all queued native buffers
|
||||
*/
|
||||
public void clear()
|
||||
{
|
||||
mTransferQueue.clear();
|
||||
mProducerQueue.clear();
|
||||
mConsumerQueue.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of complex samples of the length requested. If there are not sufficient samples in the queue
|
||||
* to fully satisfy the request, an IOException is thrown.
|
||||
* @param requestedSamples number of complex sample pairs.
|
||||
* @return a float array with twice as many (2 samples for each I & Q pair) values as requested.
|
||||
* @throws IOException if there are (temporarily) insufficient samples available.
|
||||
*/
|
||||
public float[] get(int requestedSamples) throws IOException
|
||||
{
|
||||
//Update the producer's sample quota if it changes
|
||||
if(requestedSamples != mRequestSize)
|
||||
{
|
||||
mRequestSize = requestedSamples;
|
||||
}
|
||||
|
||||
List<T> drained = new ArrayList<>();
|
||||
mTransferQueue.drainTo(drained);
|
||||
mConsumerQueue.addAll(drained);
|
||||
|
||||
int count = 0;
|
||||
|
||||
for(T buffer: mConsumerQueue)
|
||||
{
|
||||
count += buffer.sampleCount();
|
||||
}
|
||||
|
||||
if(count < requestedSamples)
|
||||
{
|
||||
throw new IOException("Insufficient samples. Please try again later");
|
||||
}
|
||||
|
||||
float[] samples = new float[requestedSamples * 2];
|
||||
int samplesPointer = 0;
|
||||
|
||||
for(T buffer: mConsumerQueue)
|
||||
{
|
||||
Iterator<InterleavedComplexSamples> iterator = buffer.iteratorInterleaved();
|
||||
|
||||
while(iterator.hasNext() && samplesPointer < samples.length)
|
||||
{
|
||||
InterleavedComplexSamples complexSamples = iterator.next();
|
||||
int toCopy = Math.min(samples.length - samplesPointer, complexSamples.samples().length);
|
||||
System.arraycopy(complexSamples.samples(), 0, samples, samplesPointer, toCopy);
|
||||
samplesPointer += toCopy;
|
||||
}
|
||||
|
||||
if(samplesPointer >= samples.length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mConsumerQueue.clear();
|
||||
return samples;
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,45 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 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.
|
||||
* 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/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* 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 io.github.dsheirer.spectrum;
|
||||
|
||||
|
||||
import io.github.dsheirer.buffer.INativeBuffer;
|
||||
import io.github.dsheirer.sample.OverflowableTransferQueue;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class OverflowableBufferStream extends OverflowableTransferQueue<ReusableComplexBuffer>
|
||||
public class OverflowableBufferStream<T extends INativeBuffer> extends OverflowableTransferQueue<T>
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(OverflowableBufferStream.class);
|
||||
|
||||
private int mFlushCount = 0;
|
||||
private ReusableComplexBuffer mCurrentBuffer;
|
||||
private Iterator<InterleavedComplexSamples> mCurrentNativeBufferIterator;
|
||||
private InterleavedComplexSamples mCurrentBuffer;
|
||||
private int mCurrentBufferPointer = 0;
|
||||
private FloatBuffer mFloatBuffer;
|
||||
private LinkedList<ReusableComplexBuffer> mBufferList = new LinkedList<>();
|
||||
private LinkedList<T> mBufferList = new LinkedList<>();
|
||||
private int mBufferFetchLimit;
|
||||
|
||||
/**
|
||||
@@ -90,7 +96,7 @@ public class OverflowableBufferStream extends OverflowableTransferQueue<Reusable
|
||||
|
||||
while(mFloatBuffer.hasRemaining())
|
||||
{
|
||||
int available = mCurrentBuffer.getSamples().length - mCurrentBufferPointer;
|
||||
int available = mCurrentBuffer.samples().length - mCurrentBufferPointer;
|
||||
|
||||
if(available <= 0)
|
||||
{
|
||||
@@ -108,7 +114,7 @@ public class OverflowableBufferStream extends OverflowableTransferQueue<Reusable
|
||||
|
||||
if(toCopy > 0)
|
||||
{
|
||||
mFloatBuffer.put(mCurrentBuffer.getSamples(), mCurrentBufferPointer, toCopy);
|
||||
mFloatBuffer.put(mCurrentBuffer.samples(), mCurrentBufferPointer, toCopy);
|
||||
mCurrentBufferPointer += toCopy;
|
||||
}
|
||||
}
|
||||
@@ -150,7 +156,7 @@ public class OverflowableBufferStream extends OverflowableTransferQueue<Reusable
|
||||
//Flush sample data as requested
|
||||
while(mFlushCount > 0)
|
||||
{
|
||||
int available = mCurrentBuffer.getSamples().length - mCurrentBufferPointer;
|
||||
int available = mCurrentBuffer.samples().length - mCurrentBufferPointer;
|
||||
|
||||
if(available <= mFlushCount)
|
||||
{
|
||||
@@ -181,35 +187,41 @@ public class OverflowableBufferStream extends OverflowableTransferQueue<Reusable
|
||||
*/
|
||||
private void getNextBuffer() throws IOException
|
||||
{
|
||||
if(mCurrentBuffer != null)
|
||||
if(mCurrentNativeBufferIterator == null)
|
||||
{
|
||||
if(mBufferList.isEmpty())
|
||||
{
|
||||
drainTo(mBufferList, mBufferFetchLimit);
|
||||
}
|
||||
|
||||
if(!mBufferList.isEmpty())
|
||||
{
|
||||
T buffer = mBufferList.poll();
|
||||
|
||||
if(buffer != null)
|
||||
{
|
||||
mCurrentNativeBufferIterator = buffer.iteratorInterleaved();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(mCurrentNativeBufferIterator != null && !mCurrentNativeBufferIterator.hasNext())
|
||||
{
|
||||
mCurrentNativeBufferIterator = null;
|
||||
getNextBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
if(mCurrentNativeBufferIterator != null && mCurrentNativeBufferIterator.hasNext())
|
||||
{
|
||||
mCurrentBuffer = mCurrentNativeBufferIterator.next();
|
||||
mCurrentBufferPointer = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Decrement the user count to let the originator know we're done with their buffer
|
||||
mCurrentBuffer.decrementUserCount();
|
||||
mCurrentBuffer = null;
|
||||
throw new IOException("Buffer queue is empty");
|
||||
}
|
||||
|
||||
mCurrentBufferPointer = 0;
|
||||
|
||||
if(mBufferList.isEmpty())
|
||||
{
|
||||
drainTo(mBufferList, mBufferFetchLimit);
|
||||
}
|
||||
|
||||
if(!mBufferList.isEmpty())
|
||||
{
|
||||
mCurrentBuffer = mBufferList.poll();
|
||||
}
|
||||
|
||||
if(mCurrentBuffer == null)
|
||||
{
|
||||
throw new IOException("Reusable complex buffer queue is (currently) empty");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void overflow(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
reusableComplexBuffer.decrementUserCount();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,14 +232,7 @@ public class OverflowableBufferStream extends OverflowableTransferQueue<Reusable
|
||||
{
|
||||
synchronized(mQueue)
|
||||
{
|
||||
ReusableComplexBuffer buffer = mQueue.poll();
|
||||
|
||||
while(buffer != null)
|
||||
{
|
||||
buffer.decrementUserCount();
|
||||
buffer = mQueue.poll();
|
||||
}
|
||||
|
||||
mQueue.clear();
|
||||
mCounter.set(0);
|
||||
mOverflow.set(false);
|
||||
}
|
||||
|
||||
@@ -1,39 +1,35 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 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/>
|
||||
* * *****************************************************************************
|
||||
* 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 io.github.dsheirer.spectrum;
|
||||
|
||||
import com.jidesoft.swing.JideSplitPane;
|
||||
import io.github.dsheirer.buffer.INativeBuffer;
|
||||
import io.github.dsheirer.controller.channel.Channel;
|
||||
import io.github.dsheirer.controller.channel.ChannelModel;
|
||||
import io.github.dsheirer.controller.channel.ChannelProcessingManager;
|
||||
import io.github.dsheirer.dsp.filter.Window.WindowType;
|
||||
import io.github.dsheirer.dsp.filter.smoothing.SmoothingFilter.SmoothingType;
|
||||
import io.github.dsheirer.dsp.window.WindowType;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.gui.playlist.channel.ViewChannelRequest;
|
||||
import io.github.dsheirer.playlist.PlaylistManager;
|
||||
import io.github.dsheirer.properties.SystemProperties;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.SampleType;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.settings.ColorSetting.ColorSettingName;
|
||||
import io.github.dsheirer.settings.ColorSettingMenuItem;
|
||||
import io.github.dsheirer.settings.SettingsManager;
|
||||
@@ -83,8 +79,8 @@ import java.awt.event.MouseWheelEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Hashtable;
|
||||
|
||||
public class SpectralDisplayPanel extends JPanel implements Listener<ReusableComplexBuffer>, ISourceEventProcessor,
|
||||
IDFTWidthChangeProcessor
|
||||
public class SpectralDisplayPanel extends JPanel
|
||||
implements Listener<INativeBuffer>, ISourceEventProcessor, IDFTWidthChangeProcessor
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@@ -104,7 +100,7 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
private SpectrumPanel mSpectrumPanel;
|
||||
private WaterfallPanel mWaterfallPanel;
|
||||
private OverlayPanel mOverlayPanel;
|
||||
private DFTProcessor mDFTProcessor;
|
||||
private ComplexDftProcessor mComplexDftProcessor;
|
||||
private DFTResultsConverter mDFTConverter;
|
||||
private ChannelModel mChannelModel;
|
||||
private ChannelProcessingManager mChannelProcessingManager;
|
||||
@@ -116,10 +112,10 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
* Spectral Display Panel provides a frequency component display with a
|
||||
* historical waterfall display and a transparent overlay to show frequency,
|
||||
* cursor and channel information.
|
||||
*
|
||||
* <p>
|
||||
* Mouse scrolling and zooming are supported and the waterfall display can
|
||||
* be paused.
|
||||
*
|
||||
* <p>
|
||||
* Complex sample buffers are processed by a DFTProcessor and the output of
|
||||
* the DFT is translated to decibels for display in the spectrum and
|
||||
* waterfall components.
|
||||
@@ -175,8 +171,8 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
|
||||
mSettingsManager = null;
|
||||
|
||||
mDFTProcessor.dispose();
|
||||
mDFTProcessor = null;
|
||||
mComplexDftProcessor.dispose();
|
||||
mComplexDftProcessor = null;
|
||||
|
||||
mDFTConverter.dispose();
|
||||
mDFTConverter = null;
|
||||
@@ -190,7 +186,6 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
mOverlayPanel.dispose();
|
||||
mOverlayPanel = null;
|
||||
|
||||
|
||||
mTuner = null;
|
||||
}
|
||||
|
||||
@@ -200,7 +195,7 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
*/
|
||||
public void setDFTSize(DFTSize size, boolean save)
|
||||
{
|
||||
mDFTProcessor.setDFTSize(size);
|
||||
mComplexDftProcessor.setDFTSize(size);
|
||||
mOverlayPanel.setDFTSize(size);
|
||||
mDFTSize = size;
|
||||
|
||||
@@ -217,8 +212,7 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
setDFTSize(size, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DFTSize getDFTSize()
|
||||
@Override public DFTSize getDFTSize()
|
||||
{
|
||||
return mDFTSize;
|
||||
}
|
||||
@@ -230,7 +224,7 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
|
||||
/**
|
||||
* Sets the current zoom level which will be 2 to the power of zoom (2^zoom)
|
||||
*
|
||||
* <p>
|
||||
* 0 No Zoom
|
||||
* 1 2x Zoom
|
||||
* 2 4x Zoom
|
||||
@@ -239,8 +233,8 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
* 5 32x Zoom
|
||||
* 6 64x Zoom
|
||||
*
|
||||
* @param zoom level, 0 - 6.
|
||||
* @param frequency under the mouse to maintain while zooming
|
||||
* @param zoom level, 0 - 6.
|
||||
* @param frequency under the mouse to maintain while zooming
|
||||
* @param windowOffset where to maintain the frequency under the mouse
|
||||
*/
|
||||
public void setZoom(int zoom, long frequency, double windowOffset)
|
||||
@@ -309,10 +303,9 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
return mDFTSize.getSize() / getZoomMultiplier();
|
||||
}
|
||||
|
||||
|
||||
public int getZoomMultiplier()
|
||||
{
|
||||
return (int) FastMath.pow(2.0, mZoom);
|
||||
return (int)FastMath.pow(2.0, mZoom);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -328,9 +321,8 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
|
||||
if(mOverlayPanel.containsFrequency(frequency))
|
||||
{
|
||||
offset = (double)mDFTSize.getSize() *
|
||||
((double)(frequency - mOverlayPanel.getMinFrequency()) /
|
||||
(double)mOverlayPanel.getBandwidth());
|
||||
offset = (double)mDFTSize.getSize() * ((double)(frequency - mOverlayPanel.getMinFrequency())
|
||||
/ (double)mOverlayPanel.getBandwidth());
|
||||
}
|
||||
|
||||
return offset;
|
||||
@@ -376,14 +368,14 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
mWaterfallPanel.addMouseWheelListener(mouser);
|
||||
|
||||
/* Attempt to set a 50/50 split preferred size for the split pane */
|
||||
double totalHeight = mLayeredPanel.getPreferredSize().getHeight() +
|
||||
mWaterfallPanel.getPreferredSize().getHeight();
|
||||
double totalHeight =
|
||||
mLayeredPanel.getPreferredSize().getHeight() + mWaterfallPanel.getPreferredSize().getHeight();
|
||||
|
||||
mLayeredPanel.setPreferredSize(new Dimension((int)mLayeredPanel
|
||||
.getPreferredSize().getWidth(), (int)(totalHeight / 2.0d)));
|
||||
mLayeredPanel.setPreferredSize(
|
||||
new Dimension((int)mLayeredPanel.getPreferredSize().getWidth(), (int)(totalHeight / 2.0d)));
|
||||
|
||||
mWaterfallPanel.setPreferredSize(new Dimension((int)mWaterfallPanel
|
||||
.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);
|
||||
@@ -399,9 +391,9 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
* Setup DFTProcessor to process samples and register the waterfall and
|
||||
* spectrum panel to receive the processed dft results
|
||||
*/
|
||||
mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
|
||||
mComplexDftProcessor = new ComplexDftProcessor();
|
||||
mDFTConverter = new ComplexDecibelConverter();
|
||||
mDFTProcessor.addConverter(mDFTConverter);
|
||||
mComplexDftProcessor.addConverter(mDFTConverter);
|
||||
|
||||
mDFTConverter.addListener((DFTResultsListener)mSpectrumPanel);
|
||||
mDFTConverter.addListener((DFTResultsListener)mWaterfallPanel);
|
||||
@@ -413,16 +405,14 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
public void process(SourceEvent event)
|
||||
{
|
||||
mOverlayPanel.process(event);
|
||||
mDFTProcessor.process(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Complex sample buffer receive method
|
||||
*/
|
||||
@Override
|
||||
public void receive(ReusableComplexBuffer reusableComplexBuffer)
|
||||
@Override public void receive(INativeBuffer nativeBuffer)
|
||||
{
|
||||
mDFTProcessor.receive(reusableComplexBuffer);
|
||||
mComplexDftProcessor.receive(nativeBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -433,9 +423,9 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
{
|
||||
clearTuner();
|
||||
|
||||
mDFTProcessor.clearBuffer();
|
||||
mComplexDftProcessor.clearBuffer();
|
||||
|
||||
mDFTProcessor.start();
|
||||
mComplexDftProcessor.start();
|
||||
|
||||
mTuner = tuner;
|
||||
|
||||
@@ -445,7 +435,7 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
mTuner.getTunerController().addListener(this);
|
||||
|
||||
//Register the dft processor to receive samples from the tuner
|
||||
mTuner.getTunerController().addBufferListener(mDFTProcessor);
|
||||
mTuner.getTunerController().addBufferListener(mComplexDftProcessor);
|
||||
|
||||
mSpectrumPanel.setSampleSize(mTuner.getSampleSize());
|
||||
|
||||
@@ -468,12 +458,12 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
mTuner.getTunerController().removeListener(this);
|
||||
|
||||
//Deregister the dft processor from receiving samples
|
||||
mTuner.getTunerController().removeBufferListener(mDFTProcessor);
|
||||
mTuner.getTunerController().removeBufferListener(mComplexDftProcessor);
|
||||
mTuner = null;
|
||||
}
|
||||
|
||||
mDFTProcessor.stop();
|
||||
mDFTProcessor.clearBuffer();
|
||||
mComplexDftProcessor.stop();
|
||||
mComplexDftProcessor.clearBuffer();
|
||||
mSpectrumPanel.clearSpectrum();
|
||||
mWaterfallPanel.clearWaterfall();
|
||||
}
|
||||
@@ -484,8 +474,7 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
*/
|
||||
public class ResizeListener implements ComponentListener
|
||||
{
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e)
|
||||
@Override public void componentResized(ComponentEvent e)
|
||||
{
|
||||
Component c = e.getComponent();
|
||||
|
||||
@@ -493,18 +482,15 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
mOverlayPanel.setBounds(0, 0, c.getWidth(), c.getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentHidden(ComponentEvent arg0)
|
||||
@Override public void componentHidden(ComponentEvent arg0)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentMoved(ComponentEvent arg0)
|
||||
@Override public void componentMoved(ComponentEvent arg0)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentShown(ComponentEvent arg0)
|
||||
@Override public void componentShown(ComponentEvent arg0)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -522,8 +508,7 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseWheelMoved(MouseWheelEvent e)
|
||||
@Override public void mouseWheelMoved(MouseWheelEvent e)
|
||||
{
|
||||
int zoom = mZoom - e.getWheelRotation();
|
||||
|
||||
@@ -534,14 +519,12 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
setZoom(zoom, frequency, windowOffset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseMoved(MouseEvent event)
|
||||
@Override public void mouseMoved(MouseEvent event)
|
||||
{
|
||||
update(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDragged(MouseEvent event)
|
||||
@Override public void mouseDragged(MouseEvent event)
|
||||
{
|
||||
update(event);
|
||||
|
||||
@@ -566,16 +549,13 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
setZoomWindowOffset(offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e)
|
||||
@Override public void mousePressed(MouseEvent e)
|
||||
{
|
||||
mDragStartX = e.getX();
|
||||
|
||||
mDFTZoomWindowOffsetAtDragStart = mDFTZoomWindowOffset;
|
||||
|
||||
mPixelsPerBin = (double)getWidth() /
|
||||
((double)(mDFTSize.getSize()) /
|
||||
(double)getZoomMultiplier());
|
||||
mPixelsPerBin = (double)getWidth() / ((double)(mDFTSize.getSize()) / (double)getZoomMultiplier());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -590,13 +570,11 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
else
|
||||
{
|
||||
mWaterfallPanel.setCursorLocation(event.getPoint());
|
||||
mWaterfallPanel.setCursorFrequency(
|
||||
mOverlayPanel.getFrequencyFromAxis(event.getPoint().x));
|
||||
mWaterfallPanel.setCursorFrequency(mOverlayPanel.getFrequencyFromAxis(event.getPoint().x));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e)
|
||||
@Override public void mouseEntered(MouseEvent e)
|
||||
{
|
||||
if(e.getComponent() == mOverlayPanel)
|
||||
{
|
||||
@@ -608,8 +586,7 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e)
|
||||
@Override public void mouseExited(MouseEvent e)
|
||||
{
|
||||
mOverlayPanel.setCursorVisible(false);
|
||||
mWaterfallPanel.setCursorVisible(false);
|
||||
@@ -618,8 +595,7 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
/**
|
||||
* Displays the context menu.
|
||||
*/
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent event)
|
||||
@Override public void mouseClicked(MouseEvent event)
|
||||
{
|
||||
if(SwingUtilities.isRightMouseButton(event))
|
||||
{
|
||||
@@ -631,8 +607,7 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
contextMenu.add(new JSeparator());
|
||||
}
|
||||
|
||||
long frequency =
|
||||
mOverlayPanel.getFrequencyFromAxis(event.getX());
|
||||
long frequency = mOverlayPanel.getFrequencyFromAxis(event.getX());
|
||||
|
||||
if(event.getComponent() == mOverlayPanel)
|
||||
{
|
||||
@@ -642,7 +617,8 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
for(Channel channel : channels)
|
||||
{
|
||||
JMenuItem viewChannel = new JMenuItem("View/Edit: " + channel.getShortTitle());
|
||||
viewChannel.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewChannelRequest(channel)));
|
||||
viewChannel.addActionListener(
|
||||
e -> MyEventBus.getGlobalEventBus().post(new ViewChannelRequest(channel)));
|
||||
channelMenu.add(viewChannel);
|
||||
}
|
||||
|
||||
@@ -659,29 +635,21 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
*/
|
||||
JMenu colorMenu = new JMenu("Color");
|
||||
|
||||
colorMenu.add(new ColorSettingMenuItem(mSettingsManager,
|
||||
ColorSettingName.CHANNEL_CONFIG));
|
||||
colorMenu.add(new ColorSettingMenuItem(mSettingsManager, ColorSettingName.CHANNEL_CONFIG));
|
||||
|
||||
colorMenu.add(new ColorSettingMenuItem(mSettingsManager,
|
||||
ColorSettingName.CHANNEL_CONFIG_PROCESSING));
|
||||
colorMenu.add(new ColorSettingMenuItem(mSettingsManager, ColorSettingName.CHANNEL_CONFIG_PROCESSING));
|
||||
|
||||
colorMenu.add(new ColorSettingMenuItem(mSettingsManager,
|
||||
ColorSettingName.CHANNEL_CONFIG_SELECTED));
|
||||
colorMenu.add(new ColorSettingMenuItem(mSettingsManager, ColorSettingName.CHANNEL_CONFIG_SELECTED));
|
||||
|
||||
colorMenu.add(new ColorSettingMenuItem(mSettingsManager,
|
||||
ColorSettingName.SPECTRUM_CURSOR));
|
||||
colorMenu.add(new ColorSettingMenuItem(mSettingsManager, ColorSettingName.SPECTRUM_CURSOR));
|
||||
|
||||
colorMenu.add(new ColorSettingMenuItem(mSettingsManager,
|
||||
ColorSettingName.SPECTRUM_LINE));
|
||||
colorMenu.add(new ColorSettingMenuItem(mSettingsManager, ColorSettingName.SPECTRUM_LINE));
|
||||
|
||||
colorMenu.add(new ColorSettingMenuItem(mSettingsManager,
|
||||
ColorSettingName.SPECTRUM_BACKGROUND));
|
||||
colorMenu.add(new ColorSettingMenuItem(mSettingsManager, ColorSettingName.SPECTRUM_BACKGROUND));
|
||||
|
||||
colorMenu.add(new ColorSettingMenuItem(mSettingsManager,
|
||||
ColorSettingName.SPECTRUM_GRADIENT_BOTTOM));
|
||||
colorMenu.add(new ColorSettingMenuItem(mSettingsManager, ColorSettingName.SPECTRUM_GRADIENT_BOTTOM));
|
||||
|
||||
colorMenu.add(new ColorSettingMenuItem(mSettingsManager,
|
||||
ColorSettingName.SPECTRUM_GRADIENT_TOP));
|
||||
colorMenu.add(new ColorSettingMenuItem(mSettingsManager, ColorSettingName.SPECTRUM_GRADIENT_TOP));
|
||||
|
||||
contextMenu.add(colorMenu);
|
||||
|
||||
@@ -697,8 +665,7 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
* Averaging menu
|
||||
*/
|
||||
JMenu averagingMenu = new JMenu("Averaging");
|
||||
averagingMenu.add(
|
||||
new AveragingItem(mSpectrumPanel, 4));
|
||||
averagingMenu.add(new AveragingItem(mSpectrumPanel, 4));
|
||||
displayMenu.add(averagingMenu);
|
||||
|
||||
/**
|
||||
@@ -706,17 +673,13 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
*/
|
||||
JMenu channelDisplayMenu = new JMenu("Channel");
|
||||
|
||||
channelDisplayMenu.add(new ChannelDisplayItem(
|
||||
mOverlayPanel, ChannelDisplay.ALL));
|
||||
channelDisplayMenu.add(new ChannelDisplayItem(
|
||||
mOverlayPanel, ChannelDisplay.ENABLED));
|
||||
channelDisplayMenu.add(new ChannelDisplayItem(
|
||||
mOverlayPanel, ChannelDisplay.NONE));
|
||||
channelDisplayMenu.add(new ChannelDisplayItem(mOverlayPanel, ChannelDisplay.ALL));
|
||||
channelDisplayMenu.add(new ChannelDisplayItem(mOverlayPanel, ChannelDisplay.ENABLED));
|
||||
channelDisplayMenu.add(new ChannelDisplayItem(mOverlayPanel, ChannelDisplay.NONE));
|
||||
|
||||
displayMenu.add(channelDisplayMenu);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* FFT width
|
||||
*/
|
||||
@@ -734,14 +697,14 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
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));
|
||||
frameRateMenu.add(new FrameRateItem(mComplexDftProcessor, 14));
|
||||
frameRateMenu.add(new FrameRateItem(mComplexDftProcessor, 16));
|
||||
frameRateMenu.add(new FrameRateItem(mComplexDftProcessor, 18));
|
||||
frameRateMenu.add(new FrameRateItem(mComplexDftProcessor, 20));
|
||||
frameRateMenu.add(new FrameRateItem(mComplexDftProcessor, 25));
|
||||
frameRateMenu.add(new FrameRateItem(mComplexDftProcessor, 30));
|
||||
frameRateMenu.add(new FrameRateItem(mComplexDftProcessor, 40));
|
||||
frameRateMenu.add(new FrameRateItem(mComplexDftProcessor, 50));
|
||||
|
||||
/**
|
||||
* FFT Window Type
|
||||
@@ -751,8 +714,7 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
|
||||
for(WindowType type : WindowType.values())
|
||||
{
|
||||
fftWindowType.add(
|
||||
new FFTWindowTypeItem(mDFTProcessor, type));
|
||||
fftWindowType.add(new FFTWindowTypeItem(mComplexDftProcessor, type));
|
||||
}
|
||||
|
||||
if(event.getComponent() != mWaterfallPanel)
|
||||
@@ -794,7 +756,7 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
|
||||
boolean separatorAdded = false;
|
||||
|
||||
for(Tuner tuner: mTunerModel.getTuners())
|
||||
for(Tuner tuner : mTunerModel.getTuners())
|
||||
{
|
||||
if(mTuner == null || mTuner != tuner)
|
||||
{
|
||||
@@ -812,15 +774,11 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
{
|
||||
if(event.getComponent() == mOverlayPanel)
|
||||
{
|
||||
contextMenu.show(mOverlayPanel,
|
||||
event.getX(),
|
||||
event.getY());
|
||||
contextMenu.show(mOverlayPanel, event.getX(), event.getY());
|
||||
}
|
||||
else
|
||||
{
|
||||
contextMenu.show(mWaterfallPanel,
|
||||
event.getX(),
|
||||
event.getY());
|
||||
contextMenu.show(mWaterfallPanel, event.getX(), event.getY());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -845,13 +803,11 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
|
||||
addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
@Override public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
EventQueue.invokeLater(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
@Override public void run()
|
||||
{
|
||||
mPausable.setPaused(!paused);
|
||||
}
|
||||
@@ -875,7 +831,7 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
mFrequency = frequency;
|
||||
mWindowOffset = windowOffset;
|
||||
|
||||
Hashtable<Integer,JComponent> labels = new Hashtable<>();
|
||||
Hashtable<Integer, JComponent> labels = new Hashtable<>();
|
||||
labels.put(0, new JLabel("1x"));
|
||||
labels.put(1, new JLabel("2x"));
|
||||
labels.put(2, new JLabel("4x"));
|
||||
@@ -893,8 +849,7 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
|
||||
this.addChangeListener(new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e)
|
||||
@Override public void stateChanged(ChangeEvent e)
|
||||
{
|
||||
setZoom(getValue(), mFrequency, mWindowOffset);
|
||||
}
|
||||
@@ -921,13 +876,11 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
|
||||
|
||||
addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
@Override public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
EventQueue.invokeLater(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
@Override public void run()
|
||||
{
|
||||
mOverlayPanel.setChannelDisplay(mChannelDisplay);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,28 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 io.github.dsheirer.spectrum.menu;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.Window.WindowType;
|
||||
import io.github.dsheirer.spectrum.DFTProcessor;
|
||||
import io.github.dsheirer.dsp.window.WindowType;
|
||||
import io.github.dsheirer.spectrum.ComplexDftProcessor;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.JCheckBoxMenuItem;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
@@ -11,29 +30,28 @@ public class FFTWindowTypeItem extends JCheckBoxMenuItem
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private DFTProcessor mDFTProcessor;
|
||||
private ComplexDftProcessor mComplexDftProcessor;
|
||||
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 )
|
||||
public FFTWindowTypeItem(ComplexDftProcessor processor, WindowType windowType)
|
||||
{
|
||||
super(windowType.toString());
|
||||
|
||||
mComplexDftProcessor = processor;
|
||||
mWindowType = windowType;
|
||||
|
||||
if(processor.getWindowType() == mWindowType)
|
||||
{
|
||||
setSelected(true);
|
||||
}
|
||||
|
||||
addActionListener(new ActionListener()
|
||||
{
|
||||
@Override public void actionPerformed(ActionEvent arg0)
|
||||
{
|
||||
mDFTProcessor.setWindowType( mWindowType );
|
||||
mComplexDftProcessor.setWindowType(mWindowType);
|
||||
}
|
||||
} );
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,25 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 io.github.dsheirer.spectrum.menu;
|
||||
|
||||
import io.github.dsheirer.spectrum.DFTProcessor;
|
||||
import io.github.dsheirer.spectrum.ComplexDftProcessor;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
@@ -10,14 +29,14 @@ public class FrameRateItem extends JCheckBoxMenuItem
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private DFTProcessor mDFTProcessor;
|
||||
private ComplexDftProcessor mComplexDftProcessor;
|
||||
private int mFrameRate;
|
||||
|
||||
public FrameRateItem( DFTProcessor processor, int frameRate )
|
||||
public FrameRateItem(ComplexDftProcessor processor, int frameRate )
|
||||
{
|
||||
super( String.valueOf( frameRate ) );
|
||||
|
||||
mDFTProcessor = processor;
|
||||
mComplexDftProcessor = processor;
|
||||
mFrameRate = frameRate;
|
||||
|
||||
if( processor.getFrameRate() == mFrameRate )
|
||||
@@ -30,7 +49,7 @@ public class FrameRateItem extends JCheckBoxMenuItem
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent arg0 )
|
||||
{
|
||||
mDFTProcessor.setFrameRate( mFrameRate );
|
||||
mComplexDftProcessor.setFrameRate( mFrameRate );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user