mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-14 01:27:34 -05:00
#39 WIP polyphase channelizers. Creates test tuner and controller to
use in testing tuner and polyphase channel manager operations.
This commit is contained in:
@@ -105,8 +105,11 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener<Co
|
|||||||
*/
|
*/
|
||||||
public void addChannel(PolyphaseChannelSource polyphaseChannelSource)
|
public void addChannel(PolyphaseChannelSource polyphaseChannelSource)
|
||||||
{
|
{
|
||||||
mChannels.add(polyphaseChannelSource);
|
if(polyphaseChannelSource != null && !mChannels.contains(polyphaseChannelSource))
|
||||||
mSourceChangeBroadcaster.addListener(polyphaseChannelSource.getSourceEventListener());
|
{
|
||||||
|
mChannels.add(polyphaseChannelSource);
|
||||||
|
mSourceChangeBroadcaster.addListener(polyphaseChannelSource.getSourceEventListener());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -116,8 +119,11 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener<Co
|
|||||||
*/
|
*/
|
||||||
public void removeChannel(PolyphaseChannelSource polyphaseChannelSource)
|
public void removeChannel(PolyphaseChannelSource polyphaseChannelSource)
|
||||||
{
|
{
|
||||||
mChannels.remove(polyphaseChannelSource);
|
if(polyphaseChannelSource != null && mChannels.contains(polyphaseChannelSource))
|
||||||
mSourceChangeBroadcaster.removeListener(polyphaseChannelSource.getSourceEventListener());
|
{
|
||||||
|
mChannels.remove(polyphaseChannelSource);
|
||||||
|
mSourceChangeBroadcaster.removeListener(polyphaseChannelSource.getSourceEventListener());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -83,6 +83,17 @@ public class ChannelIndexCalculator
|
|||||||
mChannelCount = channelCount;
|
mChannelCount = channelCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the channel count using the sample rate argument to calculate the number of channels from the
|
||||||
|
* already established channel bandwidth.
|
||||||
|
*
|
||||||
|
* @param sampleRate to use for channel count
|
||||||
|
*/
|
||||||
|
public void setSampleRate(int sampleRate)
|
||||||
|
{
|
||||||
|
setChannelCount(sampleRate / getChannelBandwidth());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Channel bandwidth. Note: this value may be smaller than the channel sample rate when oversampling
|
* Channel bandwidth. Note: this value may be smaller than the channel sample rate when oversampling
|
||||||
* is being used.
|
* is being used.
|
||||||
|
|||||||
@@ -18,10 +18,6 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package dsp.filter.channelizer;
|
package dsp.filter.channelizer;
|
||||||
|
|
||||||
import dsp.filter.FilterFactory;
|
|
||||||
import dsp.filter.Window;
|
|
||||||
import dsp.filter.design.FilterDesignException;
|
|
||||||
import dsp.mixer.LowPhaseNoiseOscillator;
|
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -30,10 +26,11 @@ import sample.SampleType;
|
|||||||
import sample.complex.ComplexBuffer;
|
import sample.complex.ComplexBuffer;
|
||||||
import settings.SettingsManager;
|
import settings.SettingsManager;
|
||||||
import source.ISourceEventProcessor;
|
import source.ISourceEventProcessor;
|
||||||
|
import source.SourceEvent;
|
||||||
import source.SourceException;
|
import source.SourceException;
|
||||||
import source.tuner.TunerChannel;
|
import source.tuner.TunerChannel;
|
||||||
import source.tuner.configuration.TunerConfigurationModel;
|
import source.tuner.configuration.TunerConfigurationModel;
|
||||||
import source.SourceEvent;
|
import source.tuner.test.TestTuner;
|
||||||
import spectrum.DFTProcessor;
|
import spectrum.DFTProcessor;
|
||||||
import spectrum.DFTSize;
|
import spectrum.DFTSize;
|
||||||
import spectrum.SpectrumPanel;
|
import spectrum.SpectrumPanel;
|
||||||
@@ -44,9 +41,6 @@ import javax.swing.*;
|
|||||||
import javax.swing.event.ChangeEvent;
|
import javax.swing.event.ChangeEvent;
|
||||||
import javax.swing.event.ChangeListener;
|
import javax.swing.event.ChangeListener;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
public class ChannelizerViewer extends JFrame
|
public class ChannelizerViewer extends JFrame
|
||||||
{
|
{
|
||||||
@@ -58,16 +52,16 @@ public class ChannelizerViewer extends JFrame
|
|||||||
private SettingsManager mSettingsManager = new SettingsManager(new TunerConfigurationModel());
|
private SettingsManager mSettingsManager = new SettingsManager(new TunerConfigurationModel());
|
||||||
private JPanel mPrimaryPanel;
|
private JPanel mPrimaryPanel;
|
||||||
private JPanel mControlPanel;
|
private JPanel mControlPanel;
|
||||||
private ChannelPanel mPrimarySpectrumPanel;
|
private PrimarySpectrumPanel mPrimarySpectrumPanel;
|
||||||
private ChannelArrayPanel mChannelPanel;
|
private ChannelArrayPanel mChannelPanel;
|
||||||
private LowPhaseNoiseOscillator mOscillator;
|
|
||||||
private int mChannelCount;
|
private int mChannelCount;
|
||||||
private int mChannelsPerRow;
|
private int mChannelsPerRow;
|
||||||
private int mFilterTapsPerChannel;
|
private int mFilterTapsPerChannel;
|
||||||
private int mSampleRate;
|
private int mSampleRate;
|
||||||
private int mToneFrequency;
|
private long mToneFrequency;
|
||||||
private DFTSize mMainPanelDFTSize = DFTSize.FFT32768;
|
private DFTSize mMainPanelDFTSize = DFTSize.FFT32768;
|
||||||
private DFTSize mChannelPanelDFTSize = DFTSize.FFT00512;
|
private DFTSize mChannelPanelDFTSize = DFTSize.FFT00512;
|
||||||
|
private TestTuner mTestTuner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GUI Test utility for researching polyphase channelizers.
|
* GUI Test utility for researching polyphase channelizers.
|
||||||
@@ -82,7 +76,13 @@ public class ChannelizerViewer extends JFrame
|
|||||||
mFilterTapsPerChannel = tapsPerChannel;
|
mFilterTapsPerChannel = tapsPerChannel;
|
||||||
|
|
||||||
mSampleRate = mChannelCount * CHANNEL_BANDWIDTH;
|
mSampleRate = mChannelCount * CHANNEL_BANDWIDTH;
|
||||||
mToneFrequency = (int)(CHANNEL_BANDWIDTH * 1); //Set to channel 1 as default
|
|
||||||
|
mTestTuner = new TestTuner();
|
||||||
|
|
||||||
|
mToneFrequency = mTestTuner.getTunerController().getFrequency() +
|
||||||
|
(int)(CHANNEL_BANDWIDTH * 1); //Set to channel 1 as default
|
||||||
|
|
||||||
|
mTestTuner.setToneFrequency(mToneFrequency);
|
||||||
|
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
@@ -95,32 +95,6 @@ public class ChannelizerViewer extends JFrame
|
|||||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||||
setLocationRelativeTo(null);
|
setLocationRelativeTo(null);
|
||||||
add(getPrimaryPanel());
|
add(getPrimaryPanel());
|
||||||
|
|
||||||
mOscillator = new LowPhaseNoiseOscillator(mSampleRate, mToneFrequency);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start()
|
|
||||||
{
|
|
||||||
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
|
|
||||||
scheduledExecutorService.scheduleAtFixedRate(new SampleGenerationTask(), 0, 50, TimeUnit.MILLISECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setToneFrequency(int frequency)
|
|
||||||
{
|
|
||||||
mOscillator.setFrequency(frequency);
|
|
||||||
mToneFrequency = frequency;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void generateSamples()
|
|
||||||
{
|
|
||||||
int size = mSampleRate / CHANNEL_FFT_FRAME_RATE;
|
|
||||||
|
|
||||||
float[] samples = mOscillator.generate(size);
|
|
||||||
|
|
||||||
ComplexBuffer buffer = new ComplexBuffer(samples);
|
|
||||||
|
|
||||||
getChannelArrayPanel().receive(buffer);
|
|
||||||
getSpectrumPanel().receive(buffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private JPanel getPrimaryPanel()
|
private JPanel getPrimaryPanel()
|
||||||
@@ -137,13 +111,14 @@ public class ChannelizerViewer extends JFrame
|
|||||||
return mPrimaryPanel;
|
return mPrimaryPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ChannelPanel getSpectrumPanel()
|
private PrimarySpectrumPanel getSpectrumPanel()
|
||||||
{
|
{
|
||||||
if(mPrimarySpectrumPanel == null)
|
if(mPrimarySpectrumPanel == null)
|
||||||
{
|
{
|
||||||
// mPrimarySpectrumPanel = new ChannelPanel(mSettingsManager, mSampleRate);
|
mPrimarySpectrumPanel = new PrimarySpectrumPanel(mSettingsManager, mSampleRate);
|
||||||
mPrimarySpectrumPanel.setPreferredSize(new Dimension(1000, 200));
|
mPrimarySpectrumPanel.setPreferredSize(new Dimension(1000, 200));
|
||||||
mPrimarySpectrumPanel.setDFTSize(mMainPanelDFTSize);
|
mPrimarySpectrumPanel.setDFTSize(mMainPanelDFTSize);
|
||||||
|
mTestTuner.addListener(mPrimarySpectrumPanel);
|
||||||
}
|
}
|
||||||
|
|
||||||
return mPrimarySpectrumPanel;
|
return mPrimarySpectrumPanel;
|
||||||
@@ -157,9 +132,11 @@ public class ChannelizerViewer extends JFrame
|
|||||||
mControlPanel.setLayout(new MigLayout("insets 0 0 0 0", "", ""));
|
mControlPanel.setLayout(new MigLayout("insets 0 0 0 0", "", ""));
|
||||||
|
|
||||||
mControlPanel.add(new JLabel("Tone:"), "align left");
|
mControlPanel.add(new JLabel("Tone:"), "align left");
|
||||||
int maximumFrequency = mSampleRate / 2;
|
int maximumFrequencyOffset = mSampleRate / 2;
|
||||||
|
long minimumFrequency = mTestTuner.getTunerController().getFrequency() - maximumFrequencyOffset;
|
||||||
|
long maximumFrequency = mTestTuner.getTunerController().getFrequency() + maximumFrequencyOffset;
|
||||||
|
|
||||||
SpinnerNumberModel model = new SpinnerNumberModel(mToneFrequency, -maximumFrequency, maximumFrequency,
|
SpinnerNumberModel model = new SpinnerNumberModel(mToneFrequency, minimumFrequency, maximumFrequency,
|
||||||
100 );
|
100 );
|
||||||
|
|
||||||
model.addChangeListener(new ChangeListener()
|
model.addChangeListener(new ChangeListener()
|
||||||
@@ -167,8 +144,8 @@ public class ChannelizerViewer extends JFrame
|
|||||||
@Override
|
@Override
|
||||||
public void stateChanged(ChangeEvent e)
|
public void stateChanged(ChangeEvent e)
|
||||||
{
|
{
|
||||||
int frequency = model.getNumber().intValue();
|
int toneFrequency = model.getNumber().intValue();
|
||||||
mOscillator.setFrequency(frequency);
|
mTestTuner.setToneFrequency(toneFrequency);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -190,35 +167,21 @@ public class ChannelizerViewer extends JFrame
|
|||||||
{
|
{
|
||||||
if(mChannelPanel == null)
|
if(mChannelPanel == null)
|
||||||
{
|
{
|
||||||
float[] taps = null;
|
mChannelPanel = new ChannelArrayPanel();
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
taps = getFilter();
|
|
||||||
}
|
|
||||||
catch(FilterDesignException fde)
|
|
||||||
{
|
|
||||||
mLog.debug("Couldn't design filter", fde);
|
|
||||||
}
|
|
||||||
|
|
||||||
mChannelPanel = new ChannelArrayPanel(taps);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return mChannelPanel;
|
return mChannelPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
private float[] getFilter() throws FilterDesignException
|
// private float[] getFilter() throws FilterDesignException
|
||||||
{
|
// {
|
||||||
return FilterFactory.getSincChannelizer(CHANNEL_BANDWIDTH, mChannelCount, mFilterTapsPerChannel,
|
// return FilterFactory.getSincChannelizer(CHANNEL_BANDWIDTH, mChannelCount, mFilterTapsPerChannel,
|
||||||
Window.WindowType.BLACKMAN_HARRIS_7, true);
|
// Window.WindowType.BLACKMAN_HARRIS_7, true);
|
||||||
}
|
// }
|
||||||
|
|
||||||
public class ChannelArrayPanel extends JPanel implements Listener<ComplexBuffer>
|
public class ChannelArrayPanel extends JPanel
|
||||||
{
|
{
|
||||||
private ComplexPolyphaseChannelizer mPolyphaseChannelizer;
|
public ChannelArrayPanel()
|
||||||
private ChannelDistributor mChannelDistributor;
|
|
||||||
|
|
||||||
public ChannelArrayPanel(float[] taps)
|
|
||||||
{
|
{
|
||||||
int bufferSize = CHANNEL_BANDWIDTH / CHANNEL_FFT_FRAME_RATE;
|
int bufferSize = CHANNEL_BANDWIDTH / CHANNEL_FFT_FRAME_RATE;
|
||||||
if(bufferSize % 2 == 1)
|
if(bufferSize % 2 == 1)
|
||||||
@@ -226,10 +189,6 @@ public class ChannelizerViewer extends JFrame
|
|||||||
bufferSize++;
|
bufferSize++;
|
||||||
}
|
}
|
||||||
|
|
||||||
mPolyphaseChannelizer = new ComplexPolyphaseChannelizer(taps, mChannelCount, CHANNEL_BANDWIDTH);
|
|
||||||
mChannelDistributor = new ChannelDistributor(bufferSize, mChannelCount);
|
|
||||||
// mPolyphaseChannelizer.setChannelDistributor(mChannelDistributor);
|
|
||||||
|
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,7 +202,8 @@ public class ChannelizerViewer extends JFrame
|
|||||||
for(int x = 0; x < mChannelCount; x++)
|
for(int x = 0; x < mChannelCount; x++)
|
||||||
{
|
{
|
||||||
//place the channels left and right of 10.0 MHz
|
//place the channels left and right of 10.0 MHz
|
||||||
long frequency = 10000000l + ((x * CHANNEL_BANDWIDTH) - halfSpectralBandwidth);
|
long frequency = mTestTuner.getTunerController().getFrequency() +
|
||||||
|
((x * CHANNEL_BANDWIDTH) - halfSpectralBandwidth);
|
||||||
ChannelPanel channelPanel = new ChannelPanel(mSettingsManager, CHANNEL_BANDWIDTH * 2, frequency, CHANNEL_BANDWIDTH);
|
ChannelPanel channelPanel = new ChannelPanel(mSettingsManager, CHANNEL_BANDWIDTH * 2, frequency, CHANNEL_BANDWIDTH);
|
||||||
channelPanel.setDFTSize(mChannelPanelDFTSize);
|
channelPanel.setDFTSize(mChannelPanelDFTSize);
|
||||||
|
|
||||||
@@ -255,28 +215,17 @@ public class ChannelizerViewer extends JFrame
|
|||||||
{
|
{
|
||||||
add(channelPanel, "grow,push");
|
add(channelPanel, "grow,push");
|
||||||
}
|
}
|
||||||
|
|
||||||
// channelPanel.getPolyphaseChannelSource()
|
|
||||||
|
|
||||||
mChannelDistributor.addListener(x, channelPanel);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void receive(ComplexBuffer complexBuffer)
|
|
||||||
{
|
|
||||||
mPolyphaseChannelizer.receive(complexBuffer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ChannelPanel extends JPanel implements Listener<ComplexBuffer>, ISourceEventProcessor
|
public class PrimarySpectrumPanel extends JPanel implements Listener<ComplexBuffer>, ISourceEventProcessor
|
||||||
{
|
{
|
||||||
private PolyphaseChannelSource mPolyphaseChannelSource;
|
|
||||||
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
|
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
|
||||||
private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter();
|
private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter();
|
||||||
private SpectrumPanel mSpectrumPanel;
|
private SpectrumPanel mSpectrumPanel;
|
||||||
|
|
||||||
public ChannelPanel(SettingsManager settingsManager, int sampleRate, long frequency, int bandwidth)
|
public PrimarySpectrumPanel(SettingsManager settingsManager, int sampleRate)
|
||||||
{
|
{
|
||||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||||
mSpectrumPanel = new SpectrumPanel(settingsManager);
|
mSpectrumPanel = new SpectrumPanel(settingsManager);
|
||||||
@@ -285,16 +234,6 @@ public class ChannelizerViewer extends JFrame
|
|||||||
mDFTProcessor.addConverter(mComplexDecibelConverter);
|
mDFTProcessor.addConverter(mComplexDecibelConverter);
|
||||||
mDFTProcessor.process(SourceEvent.sampleRateChange(sampleRate));
|
mDFTProcessor.process(SourceEvent.sampleRateChange(sampleRate));
|
||||||
mComplexDecibelConverter.addListener(mSpectrumPanel);
|
mComplexDecibelConverter.addListener(mSpectrumPanel);
|
||||||
|
|
||||||
TunerChannel tunerChannel = new TunerChannel(frequency, bandwidth);
|
|
||||||
mPolyphaseChannelSource = new PolyphaseChannelSource(this, tunerChannel);
|
|
||||||
mPolyphaseChannelSource.setListener(mDFTProcessor);
|
|
||||||
mPolyphaseChannelSource.start(ThreadPool.SCHEDULED);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PolyphaseChannelSource getPolyphaseChannelSource()
|
|
||||||
{
|
|
||||||
return mPolyphaseChannelSource;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDFTSize(DFTSize dftSize)
|
public void setDFTSize(DFTSize dftSize)
|
||||||
@@ -315,12 +254,57 @@ public class ChannelizerViewer extends JFrame
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SampleGenerationTask implements Runnable
|
public class ChannelPanel extends JPanel implements Listener<ComplexBuffer>, ISourceEventProcessor
|
||||||
{
|
{
|
||||||
@Override
|
private PolyphaseChannelSource mSource;
|
||||||
public void run()
|
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
|
||||||
|
private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter();
|
||||||
|
private SpectrumPanel mSpectrumPanel;
|
||||||
|
|
||||||
|
public ChannelPanel(SettingsManager settingsManager, int sampleRate, long frequency, int bandwidth)
|
||||||
{
|
{
|
||||||
generateSamples();
|
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||||
|
mSpectrumPanel = new SpectrumPanel(settingsManager);
|
||||||
|
add(mSpectrumPanel);
|
||||||
|
|
||||||
|
mDFTProcessor.addConverter(mComplexDecibelConverter);
|
||||||
|
mDFTProcessor.process(SourceEvent.sampleRateChange(sampleRate));
|
||||||
|
mComplexDecibelConverter.addListener(mSpectrumPanel);
|
||||||
|
|
||||||
|
TunerChannel tunerChannel = new TunerChannel(frequency, bandwidth);
|
||||||
|
mSource = (PolyphaseChannelSource)mTestTuner.getChannelSource(tunerChannel);
|
||||||
|
|
||||||
|
if(mSource != null)
|
||||||
|
{
|
||||||
|
mSource.setListener(mDFTProcessor);
|
||||||
|
mSource.start(ThreadPool.SCHEDULED);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.error("Couldn't get a source from the tuner for frequency: " + frequency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PolyphaseChannelSource getSource()
|
||||||
|
{
|
||||||
|
return mSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDFTSize(DFTSize dftSize)
|
||||||
|
{
|
||||||
|
mDFTProcessor.setDFTSize(dftSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receive(ComplexBuffer complexBuffer)
|
||||||
|
{
|
||||||
|
mDFTProcessor.receive(complexBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process(SourceEvent event) throws SourceException
|
||||||
|
{
|
||||||
|
mLog.debug("Source Event! Add handler support for this to channelizer viewer");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,7 +322,6 @@ public class ChannelizerViewer extends JFrame
|
|||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
frame.setVisible(true);
|
frame.setVisible(true);
|
||||||
frame.start();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ package dsp.filter.channelizer;
|
|||||||
import buffer.ReverseFloatCircularBuffer;
|
import buffer.ReverseFloatCircularBuffer;
|
||||||
import dsp.filter.FilterFactory;
|
import dsp.filter.FilterFactory;
|
||||||
import dsp.filter.Window;
|
import dsp.filter.Window;
|
||||||
|
import dsp.filter.design.FilterDesignException;
|
||||||
import dsp.mixer.Oscillator;
|
import dsp.mixer.Oscillator;
|
||||||
import org.jtransforms.fft.FloatFFT_1D;
|
import org.jtransforms.fft.FloatFFT_1D;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sample.complex.ComplexBuffer;
|
|
||||||
|
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
|
|
||||||
@@ -33,6 +33,8 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
|||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(ComplexPolyphaseChannelizerM2.class);
|
private final static Logger mLog = LoggerFactory.getLogger(ComplexPolyphaseChannelizerM2.class);
|
||||||
|
|
||||||
|
private static final int DEFAULT_CHANNEL_BANDWIDTH = 12500;
|
||||||
|
|
||||||
private ReverseFloatCircularBuffer[] mDataBuffers;
|
private ReverseFloatCircularBuffer[] mDataBuffers;
|
||||||
private float[][] mFilterCoefficients;
|
private float[][] mFilterCoefficients;
|
||||||
private int mFilterPointer = 0;
|
private int mFilterPointer = 0;
|
||||||
@@ -48,8 +50,10 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
|||||||
* equal to the channel bandwidth (sample rate / filters). If you need to synthesize (combine two or more
|
* equal to the channel bandwidth (sample rate / filters). If you need to synthesize (combine two or more
|
||||||
* filter outputs) a new bandwidth signal from the outputs of this filter, then the filter should be designed
|
* filter outputs) a new bandwidth signal from the outputs of this filter, then the filter should be designed
|
||||||
* as a nyquist filter with -6 dB attenuation at the channel bandwidth cutoff frequency
|
* as a nyquist filter with -6 dB attenuation at the channel bandwidth cutoff frequency
|
||||||
|
*
|
||||||
* @param channels - number of filters/channels to output. Since this filter bank oversamples each filter
|
* @param channels - number of filters/channels to output. Since this filter bank oversamples each filter
|
||||||
* output, this number must be even (divisible by the oversample rate).
|
* output, this number must be even (divisible by the oversample rate).
|
||||||
|
*
|
||||||
* @param channelSampleRate for each decimated, oversampled channel
|
* @param channelSampleRate for each decimated, oversampled channel
|
||||||
*/
|
*/
|
||||||
public ComplexPolyphaseChannelizerM2(float[] taps, int channels, int channelSampleRate)
|
public ComplexPolyphaseChannelizerM2(float[] taps, int channels, int channelSampleRate)
|
||||||
@@ -64,6 +68,72 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
|||||||
initFilters(taps);
|
initFilters(taps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non-Maximally Decimated Polyphase Filter Bank (NMDPFB) channelizer that divides the input frequency band into
|
||||||
|
* equal bandwidth channels that are each oversampled by 2x for output.
|
||||||
|
*
|
||||||
|
* Note: this constructor uses a default channel bandwidth of 12.5 kHz and produces channels with an oversampled
|
||||||
|
* channel sample rate of 25.0 kHz.
|
||||||
|
*
|
||||||
|
* @param sampleRate to be channelized.
|
||||||
|
*/
|
||||||
|
private ComplexPolyphaseChannelizerM2(int sampleRate) throws FilterDesignException
|
||||||
|
{
|
||||||
|
super(sampleRate / DEFAULT_CHANNEL_BANDWIDTH, DEFAULT_CHANNEL_BANDWIDTH);
|
||||||
|
|
||||||
|
mLog.debug("Sample Rate: " + sampleRate);
|
||||||
|
|
||||||
|
float[] filterTaps = FilterFactory.getSincChannelizer(getChannelSampleRate(), getChannelCount(),
|
||||||
|
15, Window.WindowType.BLACKMAN_HARRIS_7, true);
|
||||||
|
|
||||||
|
initFilters(filterTaps);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non-Maximally Decimated Polyphase Filter Bank (NMDPFB) channelizer with a default channel bandwidth of 12.5 kHz
|
||||||
|
* and a channel output sample rate of 25.0 kHz.
|
||||||
|
*
|
||||||
|
* @param sampleRate that is an even multiple of 25.0 kHz
|
||||||
|
* @return channelizer
|
||||||
|
* @throws IllegalArgumentException if the sample rate argument is not an even multiple of 25.0 kHz
|
||||||
|
*/
|
||||||
|
public static ComplexPolyphaseChannelizerM2 create(int sampleRate) throws IllegalArgumentException,
|
||||||
|
FilterDesignException
|
||||||
|
{
|
||||||
|
if(sampleRate % (2 * DEFAULT_CHANNEL_BANDWIDTH) != 0)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Sample rate must be an even multiple of 25 kHz");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ComplexPolyphaseChannelizerM2(sampleRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates this channelizer to use the new sample rate. This method creates a new filter suitable for the
|
||||||
|
* sample rate and reinitializes all internal data structures to prepare for processing the new sample rate.
|
||||||
|
* @param sampleRate that is a multiple of 25.0 kHz
|
||||||
|
* @throws IllegalArgumentException if the sample rate is not a multiple of 25.0 kHz
|
||||||
|
* @throws FilterDesignException if a filter cannot be designed for the target sample rate
|
||||||
|
*/
|
||||||
|
public void updateSampleRate(int sampleRate) throws IllegalArgumentException, FilterDesignException
|
||||||
|
{
|
||||||
|
if(sampleRate % (2 * DEFAULT_CHANNEL_BANDWIDTH) != 0)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Sample rate must be an even multiple of 25 kHz");
|
||||||
|
}
|
||||||
|
|
||||||
|
float[] filterTaps = FilterFactory.getSincChannelizer(getChannelSampleRate(), getChannelCount(),
|
||||||
|
15, Window.WindowType.BLACKMAN_HARRIS_7, true);
|
||||||
|
|
||||||
|
initFilters(filterTaps);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the incoming complex sample pair
|
||||||
|
*
|
||||||
|
* @param inphase sample
|
||||||
|
* @param quadrature sample
|
||||||
|
*/
|
||||||
protected void filter(float inphase, float quadrature)
|
protected void filter(float inphase, float quadrature)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ package dsp.filter.channelizer;
|
|||||||
import dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor;
|
import dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor;
|
||||||
import dsp.filter.channelizer.output.OneChannelOutputProcessor;
|
import dsp.filter.channelizer.output.OneChannelOutputProcessor;
|
||||||
import dsp.filter.channelizer.output.TwoChannelOutputProcessor;
|
import dsp.filter.channelizer.output.TwoChannelOutputProcessor;
|
||||||
|
import dsp.filter.design.FilterDesignException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sample.Listener;
|
import sample.Listener;
|
||||||
@@ -34,28 +35,29 @@ import source.tuner.TunerChannel;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
public class PolyphaseChannelManager implements ISourceEventProcessor, Listener<SourceEvent>
|
public class PolyphaseChannelManager implements Listener<SourceEvent>
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
|
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
|
||||||
private static final int CHANNEL_BANDWIDTH = 12500;
|
private static final int CHANNEL_BANDWIDTH = 12500;
|
||||||
private static final int CHANNEL_SAMPLE_RATE = CHANNEL_BANDWIDTH * 2;
|
|
||||||
private static final double CHANNEL_OVERSAMPLING = 2.0;
|
private static final double CHANNEL_OVERSAMPLING = 2.0;
|
||||||
|
private static final int CHANNEL_SAMPLE_RATE = (int)(CHANNEL_BANDWIDTH * CHANNEL_OVERSAMPLING);
|
||||||
|
|
||||||
private Tuner mTuner;
|
private Tuner mTuner;
|
||||||
private List<PolyphaseChannelSource> mChannelSources = new CopyOnWriteArrayList<>();
|
private List<PolyphaseChannelSource> mChannelSources = new CopyOnWriteArrayList<>();
|
||||||
private ChannelIndexCalculator mChannelIndexCalculator;
|
private ChannelIndexCalculator mChannelIndexCalculator;
|
||||||
private ComplexPolyphaseChannelizerM2 mPolyphaseChannelizer;
|
private ComplexPolyphaseChannelizerM2 mPolyphaseChannelizer;
|
||||||
private ChannelSourceEventListener mChannelSourceEventListener = new ChannelSourceEventListener();
|
private ChannelSourceEventListener mChannelSourceEventListener = new ChannelSourceEventListener();
|
||||||
|
private ScheduledBufferProcessor mBufferProcessor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Polyphase Channel Manager is a DDC channel manager for a tuner. This class provides DDC polyphase channel
|
* Polyphase Channel Manager is a DDC channel manager and complex buffer queue/processor for a tuner. This class
|
||||||
* sources and wraps a polyphase channelizer processing sample buffers produced by the tuner and distributing
|
* provides DDC polyphase channel sources and wraps a polyphase channelizer processing sample buffers produced by
|
||||||
* channelized sample buffers to each allocated DDC polyphase channel source. This class is responsible for
|
* the tuner and distributing channelized sample buffers to each allocated DDC polyphase channel source. This
|
||||||
* monitoring the tuner for changes in center frequency and/or sample rate and updating active DDC polyphase channel
|
* class is responsible for monitoring the tuner for changes in center frequency and/or sample rate and updating
|
||||||
* sources accordingly. This class also monitors source event requests and notifications received from active DDC
|
* active DDC polyphase channel sources accordingly. This class also monitors source event requests and
|
||||||
* polyphase channel sources to adjust sample streams as required.
|
* notifications received from active DDC polyphase channel sources to adjust sample streams as required.
|
||||||
*
|
*
|
||||||
* @param tuner providing broadband sample buffers
|
* @param tuner providing complex sample buffers
|
||||||
*/
|
*/
|
||||||
public PolyphaseChannelManager(Tuner tuner)
|
public PolyphaseChannelManager(Tuner tuner)
|
||||||
{
|
{
|
||||||
@@ -64,25 +66,29 @@ public class PolyphaseChannelManager implements ISourceEventProcessor, Listener<
|
|||||||
throw new IllegalArgumentException("Tuner argument cannot be null");
|
throw new IllegalArgumentException("Tuner argument cannot be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mTuner = tuner;
|
||||||
|
|
||||||
int sampleRate = mTuner.getTunerController().getSampleRate();
|
int sampleRate = mTuner.getTunerController().getSampleRate();
|
||||||
|
|
||||||
//Ensure that tuner sample rate is a multiple of channel sample rate since polyphase channelizer is M2
|
//Ensure that tuner sample rate is a multiple of channel sample rate since polyphase channelizer is M2
|
||||||
if(sampleRate % (CHANNEL_SAMPLE_RATE) != 0)
|
if(sampleRate % (CHANNEL_SAMPLE_RATE) != 0)
|
||||||
{
|
{
|
||||||
throw new IllegalArgumentException("Tuner sample rate [" + sampleRate + "] must be a multiple of " +
|
throw new IllegalArgumentException("Tuner sample rate [" + sampleRate + "] must be an even multiple of " +
|
||||||
CHANNEL_SAMPLE_RATE + " Hz");
|
CHANNEL_SAMPLE_RATE + " Hz");
|
||||||
}
|
}
|
||||||
|
|
||||||
mTuner = tuner;
|
|
||||||
|
|
||||||
int channelCount = sampleRate / CHANNEL_BANDWIDTH;
|
|
||||||
|
|
||||||
long frequency = mTuner.getTunerController().getFrequency();
|
long frequency = mTuner.getTunerController().getFrequency();
|
||||||
|
int channelCount = sampleRate / CHANNEL_BANDWIDTH;
|
||||||
|
|
||||||
mChannelIndexCalculator = new ChannelIndexCalculator(frequency, channelCount, CHANNEL_BANDWIDTH,
|
mChannelIndexCalculator = new ChannelIndexCalculator(frequency, channelCount, CHANNEL_BANDWIDTH,
|
||||||
CHANNEL_OVERSAMPLING);
|
CHANNEL_OVERSAMPLING);
|
||||||
|
|
||||||
|
mBufferProcessor = new ScheduledBufferProcessor(500, 100, 100, 50);
|
||||||
|
|
||||||
|
updateChannelizer(sampleRate, null);
|
||||||
|
|
||||||
mTuner.addSourceEventListener(this);
|
mTuner.addSourceEventListener(this);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -103,7 +109,9 @@ public class PolyphaseChannelManager implements ISourceEventProcessor, Listener<
|
|||||||
|
|
||||||
if(outputProcessor != null)
|
if(outputProcessor != null)
|
||||||
{
|
{
|
||||||
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, this, CHANNEL_SAMPLE_RATE);
|
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
|
||||||
|
CHANNEL_SAMPLE_RATE);
|
||||||
|
|
||||||
mChannelSources.add(channelSource);
|
mChannelSources.add(channelSource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,7 +125,13 @@ public class PolyphaseChannelManager implements ISourceEventProcessor, Listener<
|
|||||||
return channelSource;
|
return channelSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IPolyphaseChannelOutputProcessor getOutputProcessor(List<Integer> indexes) throws IllegalArgumentException
|
/**
|
||||||
|
* Creates a processor to process the channelizer channel indexes into a composite output stream providing
|
||||||
|
* channelized complex sample buffers to a registered source listener.
|
||||||
|
* @param indexes to target by the output processor
|
||||||
|
* @return output processor compatible with the number of indexes to monitor
|
||||||
|
*/
|
||||||
|
private IPolyphaseChannelOutputProcessor getOutputProcessor(List<Integer> indexes)
|
||||||
{
|
{
|
||||||
switch(indexes.size())
|
switch(indexes.size())
|
||||||
{
|
{
|
||||||
@@ -127,6 +141,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor, Listener<
|
|||||||
return new TwoChannelOutputProcessor(CHANNEL_SAMPLE_RATE, indexes);
|
return new TwoChannelOutputProcessor(CHANNEL_SAMPLE_RATE, indexes);
|
||||||
default:
|
default:
|
||||||
//TODO: create output processor for greater than 2 input channels
|
//TODO: create output processor for greater than 2 input channels
|
||||||
|
mLog.error("Request to create an output processor for unexpected channel index size:" + indexes.size());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,11 +154,15 @@ public class PolyphaseChannelManager implements ISourceEventProcessor, Listener<
|
|||||||
*/
|
*/
|
||||||
private void startChannelSource(PolyphaseChannelSource channelSource)
|
private void startChannelSource(PolyphaseChannelSource channelSource)
|
||||||
{
|
{
|
||||||
mPolyphaseChannelizer.addChannel(channelSource);
|
synchronized(mBufferProcessor)
|
||||||
|
|
||||||
if(mPolyphaseChannelizer.getRegisteredChannelCount() == 1)
|
|
||||||
{
|
{
|
||||||
mTuner.addListener(mPolyphaseChannelizer);
|
mPolyphaseChannelizer.addChannel(channelSource);
|
||||||
|
|
||||||
|
if(mPolyphaseChannelizer.getRegisteredChannelCount() == 1)
|
||||||
|
{
|
||||||
|
mTuner.addListener(mBufferProcessor);
|
||||||
|
mBufferProcessor.start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,43 +174,91 @@ public class PolyphaseChannelManager implements ISourceEventProcessor, Listener<
|
|||||||
*/
|
*/
|
||||||
private void stopChannelSource(PolyphaseChannelSource channelSource)
|
private void stopChannelSource(PolyphaseChannelSource channelSource)
|
||||||
{
|
{
|
||||||
mChannelSources.remove(channelSource);
|
synchronized(mBufferProcessor)
|
||||||
|
|
||||||
if(mPolyphaseChannelizer.getRegisteredChannelCount() == 0)
|
|
||||||
{
|
{
|
||||||
mTuner.removeListener(mPolyphaseChannelizer);
|
mChannelSources.remove(channelSource);
|
||||||
|
|
||||||
|
if(mPolyphaseChannelizer.getRegisteredChannelCount() == 0)
|
||||||
|
{
|
||||||
|
mTuner.removeListener(mBufferProcessor);
|
||||||
|
mBufferProcessor.stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes source event notifications from the tuner or source event requests from any of the child polyphase
|
* Implements the Listener<SourceEvent> interface for receiving notifications of frequency and sample rate
|
||||||
* channel sources.
|
* changes from the tuner.
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void process(SourceEvent sourceEvent)
|
|
||||||
{
|
|
||||||
switch(sourceEvent.getEvent())
|
|
||||||
{
|
|
||||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
|
||||||
mChannelIndexCalculator.setCenterFrequency(sourceEvent.getValue().longValue());
|
|
||||||
updateOutputProcessors(sourceEvent);
|
|
||||||
break;
|
|
||||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
|
||||||
int sampleRate = sourceEvent.getValue().intValue();
|
|
||||||
int channelCount = sampleRate / mChannelIndexCalculator.getChannelBandwidth();
|
|
||||||
mChannelIndexCalculator.setChannelCount(channelCount);
|
|
||||||
updateOutputProcessors(sourceEvent);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements the Listener<SourceEvent> interface by delegating to the process(sourceEvent) method.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void receive(SourceEvent sourceEvent)
|
public void receive(SourceEvent sourceEvent)
|
||||||
{
|
{
|
||||||
process(sourceEvent);
|
switch(sourceEvent.getEvent())
|
||||||
|
{
|
||||||
|
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||||
|
//Update the index calculator and each of the output processors to update each channel frequency
|
||||||
|
mChannelIndexCalculator.setCenterFrequency(sourceEvent.getValue().longValue());
|
||||||
|
updateOutputProcessors(sourceEvent);
|
||||||
|
break;
|
||||||
|
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||||
|
//Update the channelizer (and the output processors) for the new sample rate
|
||||||
|
updateChannelizer(sourceEvent.getValue().intValue(), sourceEvent);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates or updates the channelizer to process the incoming sample rate and updates any channel processors.
|
||||||
|
*
|
||||||
|
* @param sampleRate to use for the channelizer
|
||||||
|
* @param sourceEvent containing a notification of sample rate change (optional - can be null). This source
|
||||||
|
* event will be passed to all output processors to ensure the change is fully promulgated before restarting the
|
||||||
|
* buffer processor if it was already running.
|
||||||
|
*/
|
||||||
|
private void updateChannelizer(int sampleRate, SourceEvent sourceEvent)
|
||||||
|
{
|
||||||
|
mChannelIndexCalculator.setSampleRate(sampleRate);
|
||||||
|
|
||||||
|
//Lock on the buffer processor to block any channel create/start/stop requests while we configure
|
||||||
|
synchronized(mBufferProcessor)
|
||||||
|
{
|
||||||
|
//Get current processor state so we can restart it after we update the channelizer
|
||||||
|
boolean bufferProcessorWasRunning = mBufferProcessor.isRunning();
|
||||||
|
|
||||||
|
//Stop will be ignored if it is not currently running
|
||||||
|
mBufferProcessor.stop();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if(mPolyphaseChannelizer == null)
|
||||||
|
{
|
||||||
|
mPolyphaseChannelizer = ComplexPolyphaseChannelizerM2.create(sampleRate);
|
||||||
|
mBufferProcessor.setListener(mPolyphaseChannelizer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mPolyphaseChannelizer.updateSampleRate(sampleRate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(IllegalArgumentException iae)
|
||||||
|
{
|
||||||
|
mLog.error("Could not create polyphase channelizer for sample rate [" + sampleRate + "]", iae);
|
||||||
|
}
|
||||||
|
catch(FilterDesignException fde)
|
||||||
|
{
|
||||||
|
mLog.error("Could not create filter for polyphase channelizer for sample rate [" + sampleRate + "]", fde);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sourceEvent != null && sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_SAMPLE_RATE_CHANGE)
|
||||||
|
{
|
||||||
|
updateOutputProcessors(sourceEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(bufferProcessorWasRunning)
|
||||||
|
{
|
||||||
|
mBufferProcessor.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
161
src/dsp/filter/channelizer/ScheduledBufferProcessor.java
Normal file
161
src/dsp/filter/channelizer/ScheduledBufferProcessor.java
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* sdrtrunk
|
||||||
|
* Copyright (C) 2014-2017 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 dsp.filter.channelizer;
|
||||||
|
|
||||||
|
import sample.Listener;
|
||||||
|
import sample.OverflowableTransferQueue;
|
||||||
|
import sample.real.IOverflowListener;
|
||||||
|
import util.ThreadPool;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
public class ScheduledBufferProcessor<E> implements Listener<E>
|
||||||
|
{
|
||||||
|
private OverflowableTransferQueue<E> mQueue;
|
||||||
|
private Listener<E> mListener;
|
||||||
|
private ScheduledFuture<?> mScheduledFuture;
|
||||||
|
private long mDistributionInterval;
|
||||||
|
private int mMaxBuffersPerInterval;
|
||||||
|
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scheduled Buffer Processor combines an internal overflowable buffer with a scheduled runnable processing task
|
||||||
|
* for periodically distributing internally queued elements to the registered listener. This processor provides
|
||||||
|
* a convenient way to create a thread-safe buffer for receiving elements from one thread/runnable and then
|
||||||
|
* distributing those elements to a registered listener where distribution occurs on a separate scheduled thread
|
||||||
|
* pool runnable thread. This allows the calling input thread to quickly return without incurring any subsequent
|
||||||
|
* processing workload.
|
||||||
|
*
|
||||||
|
* The internal queue is an overflowable queue implementation that allows a listener to be registered to receive
|
||||||
|
* notifications of overflow and reset state. Queue sizing parameters are specified in the constructor.
|
||||||
|
*
|
||||||
|
* @param maximumSize of the internal queue (overflow happens when this is exceeded)
|
||||||
|
* @param resetThreshold of the internal queue (overflow reset happens once queue size falls below this threshold
|
||||||
|
* @param distributionInterval in milliseconds specifying how often the internal scheduled thread pool buffer
|
||||||
|
* distributor should run
|
||||||
|
* @param maxBuffersPerInterval maximum number of buffers to pull from the internal queue during each distribution
|
||||||
|
* interval
|
||||||
|
*/
|
||||||
|
public ScheduledBufferProcessor(int maximumSize, int resetThreshold, long distributionInterval, int maxBuffersPerInterval)
|
||||||
|
{
|
||||||
|
mQueue = new OverflowableTransferQueue<>(maximumSize, resetThreshold);
|
||||||
|
mDistributionInterval = distributionInterval;
|
||||||
|
mMaxBuffersPerInterval = maxBuffersPerInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the listener to receive notifications of buffer overflow and/or reset. Note: this method can also be
|
||||||
|
* invoked with a null argument to clear the previously registered overflow listener.
|
||||||
|
*
|
||||||
|
* @param listener to receive overflow/reset notifications.
|
||||||
|
*/
|
||||||
|
public void setOverflowListener(IOverflowListener listener)
|
||||||
|
{
|
||||||
|
mQueue.setOverflowListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets or changes the listener to receive buffers from this processor.
|
||||||
|
* @param listener to receive buffers
|
||||||
|
*/
|
||||||
|
public void setListener(Listener<E> listener)
|
||||||
|
{
|
||||||
|
mListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primary input method for adding buffers to this processor. Note: incoming buffers will be ignored if this
|
||||||
|
* processor is in a stopped state. You must invoke start() to allow incoming buffers and initiate buffer
|
||||||
|
* processing.
|
||||||
|
*
|
||||||
|
* @param e to enqueue for distribution to a registered listener
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void receive(E e)
|
||||||
|
{
|
||||||
|
if(mRunning.get())
|
||||||
|
{
|
||||||
|
mQueue.offer(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts this buffer processor and allows queuing of incoming buffers.
|
||||||
|
*/
|
||||||
|
public void start()
|
||||||
|
{
|
||||||
|
if(mRunning.compareAndSet(false, true))
|
||||||
|
{
|
||||||
|
mScheduledFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new Processor(), 0, mDistributionInterval, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops this buffer processor, stops queuing of incoming buffers and clears the internal buffer queue.
|
||||||
|
*/
|
||||||
|
public void stop()
|
||||||
|
{
|
||||||
|
if(mRunning.compareAndSet(true, false))
|
||||||
|
{
|
||||||
|
if(mScheduledFuture != null)
|
||||||
|
{
|
||||||
|
mScheduledFuture.cancel(true);
|
||||||
|
mScheduledFuture = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
mQueue.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this processor is currently running
|
||||||
|
*/
|
||||||
|
public boolean isRunning()
|
||||||
|
{
|
||||||
|
return mRunning.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processor to service the buffer queue and distribute the buffers to the registered listener
|
||||||
|
*/
|
||||||
|
class Processor implements Runnable
|
||||||
|
{
|
||||||
|
private List<E> mBuffers = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
mQueue.drainTo(mBuffers, mMaxBuffersPerInterval);
|
||||||
|
|
||||||
|
if(mListener != null)
|
||||||
|
{
|
||||||
|
for(E buffer: mBuffers)
|
||||||
|
{
|
||||||
|
mListener.receive(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mBuffers.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@ public class LowPhaseNoiseOscillator
|
|||||||
private static final double TWO_PI = 2.0 * Math.PI;
|
private static final double TWO_PI = 2.0 * Math.PI;
|
||||||
private static final float THREE_HALVES = 3.0f / 2.0f;
|
private static final float THREE_HALVES = 3.0f / 2.0f;
|
||||||
private int mSampleRate;
|
private int mSampleRate;
|
||||||
private int mFrequency;
|
private long mFrequency;
|
||||||
private float mInphase = 1.0f;
|
private float mInphase = 1.0f;
|
||||||
private float mQuadrature = 0.0f;
|
private float mQuadrature = 0.0f;
|
||||||
private float mPreviousInphase = 1.0f;
|
private float mPreviousInphase = 1.0f;
|
||||||
@@ -46,7 +46,7 @@ public class LowPhaseNoiseOscillator
|
|||||||
* @param sampleRate for the oscillator
|
* @param sampleRate for the oscillator
|
||||||
* @param frequency to generate
|
* @param frequency to generate
|
||||||
*/
|
*/
|
||||||
public LowPhaseNoiseOscillator(int sampleRate, int frequency)
|
public LowPhaseNoiseOscillator(int sampleRate, long frequency)
|
||||||
{
|
{
|
||||||
mSampleRate = sampleRate;
|
mSampleRate = sampleRate;
|
||||||
mFrequency = frequency;
|
mFrequency = frequency;
|
||||||
@@ -54,18 +54,40 @@ public class LowPhaseNoiseOscillator
|
|||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the sample rate for this oscillator
|
||||||
|
*/
|
||||||
public void setSampleRate(int sampleRate)
|
public void setSampleRate(int sampleRate)
|
||||||
{
|
{
|
||||||
mSampleRate = sampleRate;
|
mSampleRate = sampleRate;
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFrequency(int frequency)
|
/**
|
||||||
|
* Sample rate for this oscillator
|
||||||
|
*/
|
||||||
|
public int getSampleRate()
|
||||||
|
{
|
||||||
|
return mSampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the tone frequency generated by this oscillator
|
||||||
|
*/
|
||||||
|
public void setFrequency(long frequency)
|
||||||
{
|
{
|
||||||
mFrequency = frequency;
|
mFrequency = frequency;
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current tone frequency for this oscillator
|
||||||
|
*/
|
||||||
|
public long getFrequency()
|
||||||
|
{
|
||||||
|
return mFrequency;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the internal values after a frequency or sample rate change
|
* Updates the internal values after a frequency or sample rate change
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* SDR Trunk
|
||||||
* Copyright (C) 2014 Dennis Sheirer
|
* Copyright (C) 2014 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
@@ -25,62 +25,100 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
|||||||
*/
|
*/
|
||||||
public class Broadcaster<T> implements Listener<T>
|
public class Broadcaster<T> implements Listener<T>
|
||||||
{
|
{
|
||||||
private List<Listener<T>> mListeners = new CopyOnWriteArrayList<>();
|
private List<Listener<T>> mListeners = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public void receive( T t )
|
* Implements the Listener<T> interface to receive an element and broadcast that element to all registered
|
||||||
|
* listeners.
|
||||||
|
*
|
||||||
|
* @param t element to broadcast
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void receive(T t)
|
||||||
{
|
{
|
||||||
broadcast( t );
|
broadcast(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear listeners to prepare for garbage collection
|
|
||||||
*/
|
|
||||||
public void dispose()
|
|
||||||
{
|
|
||||||
mListeners.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasListeners()
|
|
||||||
{
|
|
||||||
return !mListeners.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getListenerCount()
|
|
||||||
{
|
|
||||||
return mListeners.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Listener<T>> getListeners()
|
|
||||||
{
|
|
||||||
return mListeners;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addListener( Listener<T> listener )
|
|
||||||
{
|
|
||||||
if(listener == null)
|
|
||||||
{
|
|
||||||
throw new IllegalArgumentException("Listener cannot be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
mListeners.add( listener );
|
/**
|
||||||
}
|
* Clear listeners to prepare for garbage collection
|
||||||
|
*/
|
||||||
public void removeListener( Listener<T> listener )
|
public void dispose()
|
||||||
{
|
|
||||||
mListeners.remove( listener );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clear()
|
|
||||||
{
|
|
||||||
mListeners.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void broadcast( T t )
|
|
||||||
{
|
{
|
||||||
for( Listener<T> listener: mListeners )
|
mListeners.clear();
|
||||||
{
|
}
|
||||||
listener.receive( t );
|
|
||||||
}
|
/**
|
||||||
|
* Indicates if this broadcaster has any listeners registered
|
||||||
|
*/
|
||||||
|
public boolean hasListeners()
|
||||||
|
{
|
||||||
|
return !mListeners.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the listener is currently registered with this broadcaster
|
||||||
|
*/
|
||||||
|
public boolean hasListener(Listener<T> listener)
|
||||||
|
{
|
||||||
|
return listener != null && mListeners.contains(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The count of listeners currently registered with this broadcaster
|
||||||
|
*/
|
||||||
|
public int getListenerCount()
|
||||||
|
{
|
||||||
|
return mListeners.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of listeners currently registered with this broadcaster
|
||||||
|
*/
|
||||||
|
public List<Listener<T>> getListeners()
|
||||||
|
{
|
||||||
|
return mListeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the listener to receive elements from this broadcaster
|
||||||
|
*
|
||||||
|
* @param listener
|
||||||
|
*/
|
||||||
|
public void addListener(Listener<T> listener)
|
||||||
|
{
|
||||||
|
if(listener != null && !mListeners.contains(listener))
|
||||||
|
{
|
||||||
|
mListeners.add(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deregisters the listener from receiving elements from this broadcaster
|
||||||
|
*/
|
||||||
|
public void removeListener(Listener<T> listener)
|
||||||
|
{
|
||||||
|
if(listener != null && mListeners.contains(listener))
|
||||||
|
{
|
||||||
|
mListeners.remove(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deregisters all listeners from this broadcaster
|
||||||
|
*/
|
||||||
|
public void clear()
|
||||||
|
{
|
||||||
|
mListeners.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcasts the element to all registered listeners
|
||||||
|
*/
|
||||||
|
public void broadcast(T t)
|
||||||
|
{
|
||||||
|
for(Listener<T> listener : mListeners)
|
||||||
|
{
|
||||||
|
listener.receive(t);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,4 +54,11 @@ public class InvalidFrequencyException extends SourceException
|
|||||||
{
|
{
|
||||||
return mValidFrequency;
|
return mValidFrequency;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage()
|
||||||
|
{
|
||||||
|
return super.getMessage() + " Invalid Frequency: " + getInvalidFrequency() +
|
||||||
|
" - current valid frequency:" + getValidFrequency();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ public abstract class MixerTuner extends Tuner implements Listener<ComplexBuffer
|
|||||||
{
|
{
|
||||||
super.removeListener( listener );
|
super.removeListener( listener );
|
||||||
|
|
||||||
if( mSampleListeners.isEmpty() )
|
if( !hasListeners() )
|
||||||
{
|
{
|
||||||
mComplexMixer.stop();
|
mComplexMixer.stop();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,11 +41,7 @@ public abstract class Tuner implements ITunerChannelProvider
|
|||||||
|
|
||||||
private String mName;
|
private String mName;
|
||||||
private TunerController mTunerController;
|
private TunerController mTunerController;
|
||||||
/**
|
private Broadcaster<ComplexBuffer> mSampleBroadcaster = new Broadcaster<>();
|
||||||
* Sample Listeners - these will typically be the DFT processor for spectral
|
|
||||||
* display, or it will be one or more tuner channel sources
|
|
||||||
*/
|
|
||||||
protected List<Listener<ComplexBuffer>> mSampleListeners = new CopyOnWriteArrayList<>();
|
|
||||||
protected List<Listener<TunerEvent>> mTunerChangeListeners = new ArrayList<>();
|
protected List<Listener<TunerEvent>> mTunerChangeListeners = new ArrayList<>();
|
||||||
protected Broadcaster<SourceEvent> mSourceEventBroadcaster = new Broadcaster<>();
|
protected Broadcaster<SourceEvent> mSourceEventBroadcaster = new Broadcaster<>();
|
||||||
|
|
||||||
@@ -89,7 +85,7 @@ public abstract class Tuner implements ITunerChannelProvider
|
|||||||
|
|
||||||
public void dispose()
|
public void dispose()
|
||||||
{
|
{
|
||||||
mSampleListeners.clear();
|
mSampleBroadcaster.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setName(String name)
|
public void setName(String name)
|
||||||
@@ -161,7 +157,7 @@ public abstract class Tuner implements ITunerChannelProvider
|
|||||||
*/
|
*/
|
||||||
public void addListener(Listener<ComplexBuffer> listener)
|
public void addListener(Listener<ComplexBuffer> listener)
|
||||||
{
|
{
|
||||||
mSampleListeners.add(listener);
|
mSampleBroadcaster.addListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -169,7 +165,15 @@ public abstract class Tuner implements ITunerChannelProvider
|
|||||||
*/
|
*/
|
||||||
public void removeListener(Listener<ComplexBuffer> listener)
|
public void removeListener(Listener<ComplexBuffer> listener)
|
||||||
{
|
{
|
||||||
mSampleListeners.remove(listener);
|
mSampleBroadcaster.removeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides access to the internal sample broadcaster for sub-class use (ie protected).
|
||||||
|
*/
|
||||||
|
protected Broadcaster<ComplexBuffer> getSampleBroadcaster()
|
||||||
|
{
|
||||||
|
return mSampleBroadcaster;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -179,7 +183,15 @@ public abstract class Tuner implements ITunerChannelProvider
|
|||||||
*/
|
*/
|
||||||
public boolean hasListener(Listener<ComplexBuffer> listener)
|
public boolean hasListener(Listener<ComplexBuffer> listener)
|
||||||
{
|
{
|
||||||
return mSampleListeners.contains(listener);
|
return mSampleBroadcaster.hasListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this tuner has any complex buffer listeners registered
|
||||||
|
*/
|
||||||
|
public boolean hasListeners()
|
||||||
|
{
|
||||||
|
return mSampleBroadcaster.hasListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -187,10 +199,7 @@ public abstract class Tuner implements ITunerChannelProvider
|
|||||||
*/
|
*/
|
||||||
public void broadcast(ComplexBuffer sampleBuffer)
|
public void broadcast(ComplexBuffer sampleBuffer)
|
||||||
{
|
{
|
||||||
for(Listener<ComplexBuffer> listener : mSampleListeners)
|
mSampleBroadcaster.broadcast(sampleBuffer);
|
||||||
{
|
|
||||||
listener.receive(sampleBuffer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ public enum TunerClass
|
|||||||
TERRATEC_NOXON_REV2( TunerType.ELONICS_E4000, "0CCD", "00E0", "Terratec", "NOXON R2" ),
|
TERRATEC_NOXON_REV2( TunerType.ELONICS_E4000, "0CCD", "00E0", "Terratec", "NOXON R2" ),
|
||||||
TERRATEC_T_STICK_PLUS( TunerType.ELONICS_E4000, "0CCD", "00D7", "Terratec", "T Stick Plus" ),
|
TERRATEC_T_STICK_PLUS( TunerType.ELONICS_E4000, "0CCD", "00D7", "Terratec", "T Stick Plus" ),
|
||||||
TWINTECH_UT40( TunerType.FITIPOWER_FC0013, "1B80", "D3A4", "Twintech", "UT-40" ),
|
TWINTECH_UT40( TunerType.FITIPOWER_FC0013, "1B80", "D3A4", "Twintech", "UT-40" ),
|
||||||
ZAAPA_ZTMINDVBZP( TunerType.FITIPOWER_FC0012, "1B80", "D398", "Zaapa", "ZT-MINDVBZP" ),
|
ZAAPA_ZTMINDVBZP( TunerType.FITIPOWER_FC0012, "1B80", "D398", "Zaapa", "ZT-MINDVBZP" ),
|
||||||
|
TEST_TUNER(TunerType.TEST, "0", "0", "ABC Tuners Inc.", "Model XYZ"),
|
||||||
UNKNOWN( TunerType.UNKNOWN, "0", "0", "Unknown Manufacturer", "Unknown Device" );
|
UNKNOWN( TunerType.UNKNOWN, "0", "0", "Unknown Manufacturer", "Unknown Device" );
|
||||||
|
|
||||||
private TunerType mTunerType;
|
private TunerType mTunerType;
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor
|
|||||||
|
|
||||||
public int getSampleRate()
|
public int getSampleRate()
|
||||||
{
|
{
|
||||||
return mFrequencyController.getBandwidth();
|
return mFrequencyController.getSampleRate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getFrequencyCorrection()
|
public double getFrequencyCorrection()
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ public enum TunerType
|
|||||||
RAFAELMICRO_R820T( "R820T" ),
|
RAFAELMICRO_R820T( "R820T" ),
|
||||||
RAFAELMICRO_R828D( "R828D" ),
|
RAFAELMICRO_R828D( "R828D" ),
|
||||||
RTL2832_VARIOUS( "Generic" ),
|
RTL2832_VARIOUS( "Generic" ),
|
||||||
|
TEST("Test"),
|
||||||
UNKNOWN( "Unknown" );
|
UNKNOWN( "Unknown" );
|
||||||
|
|
||||||
private String mLabel;
|
private String mLabel;
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ public class FrequencyController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get bandwidth/sample rate in hertz
|
* Get bandwidth in hertz
|
||||||
*/
|
*/
|
||||||
public int getBandwidth()
|
public int getBandwidth()
|
||||||
{
|
{
|
||||||
@@ -67,6 +67,14 @@ public class FrequencyController
|
|||||||
broadcastSampleRateChange();
|
broadcastSampleRateChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get sample rate in hertz
|
||||||
|
*/
|
||||||
|
public int getSampleRate()
|
||||||
|
{
|
||||||
|
return mSampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get frequency in hertz
|
* Get frequency in hertz
|
||||||
*/
|
*/
|
||||||
|
|||||||
147
src/source/tuner/test/SampleGenerator.java
Normal file
147
src/source/tuner/test/SampleGenerator.java
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* sdrtrunk
|
||||||
|
* Copyright (C) 2014-2017 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 source.tuner.test;
|
||||||
|
|
||||||
|
import dsp.mixer.LowPhaseNoiseOscillator;
|
||||||
|
import sample.Listener;
|
||||||
|
import sample.complex.ComplexBuffer;
|
||||||
|
import util.ThreadPool;
|
||||||
|
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class SampleGenerator
|
||||||
|
{
|
||||||
|
private LowPhaseNoiseOscillator mOscillator;
|
||||||
|
private Listener<ComplexBuffer> mListener;
|
||||||
|
private long mInterval;
|
||||||
|
private int mSamplesPerInterval;
|
||||||
|
private ScheduledFuture<?> mScheduledFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates complex sample buffers at the specified sample rate with a unity gain tone at the specified
|
||||||
|
* frequency. This generator runs via a scheduled thread pool and generates samples at the specified time
|
||||||
|
* interval.
|
||||||
|
*
|
||||||
|
* @param sampleRate of the complex samples
|
||||||
|
* @param frequency of the tone produced by the generator
|
||||||
|
* @param interval in milliseconds for generating samples
|
||||||
|
*/
|
||||||
|
public SampleGenerator(int sampleRate, long frequency, long interval)
|
||||||
|
{
|
||||||
|
mOscillator = new LowPhaseNoiseOscillator(sampleRate, frequency);
|
||||||
|
mInterval = interval;
|
||||||
|
|
||||||
|
updateSamplesPerInterval();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the number of complex samples generated per interval
|
||||||
|
*/
|
||||||
|
private void updateSamplesPerInterval()
|
||||||
|
{
|
||||||
|
mSamplesPerInterval = (int)((double)mOscillator.getSampleRate() * ((double)mInterval / 1000.0) * 2.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the generator producing samples
|
||||||
|
*/
|
||||||
|
public void start()
|
||||||
|
{
|
||||||
|
if(mScheduledFuture == null)
|
||||||
|
{
|
||||||
|
mScheduledFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new Generator(), 0, mInterval,
|
||||||
|
TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new IllegalStateException("Sample generator is already started");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the generator from producing samples
|
||||||
|
*/
|
||||||
|
public void stop()
|
||||||
|
{
|
||||||
|
if(mScheduledFuture != null)
|
||||||
|
{
|
||||||
|
mScheduledFuture.cancel(true);
|
||||||
|
mScheduledFuture = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new IllegalStateException("Sample generator is already stopped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the listener to receive samples once the generator starts. Note: invoke this method with null
|
||||||
|
* argument to de-register the listener.
|
||||||
|
*
|
||||||
|
* @param listener to receive complex sample buffers
|
||||||
|
*/
|
||||||
|
public void setListener(Listener<ComplexBuffer> listener)
|
||||||
|
{
|
||||||
|
mListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the tone frequency for this generator
|
||||||
|
* @param frequency to generate
|
||||||
|
*/
|
||||||
|
public void setFrequency(long frequency)
|
||||||
|
{
|
||||||
|
mOscillator.setFrequency(frequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current tone frequency for this generator
|
||||||
|
*/
|
||||||
|
public long getFrequency()
|
||||||
|
{
|
||||||
|
return mOscillator.getFrequency();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the sample rate for this generator
|
||||||
|
* @param sampleRate in hertz for complex samples
|
||||||
|
*/
|
||||||
|
public void setSampleRate(int sampleRate)
|
||||||
|
{
|
||||||
|
mOscillator.setSampleRate(sampleRate);
|
||||||
|
updateSamplesPerInterval();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a complex sample buffer and distributes the buffer to a registered listener
|
||||||
|
*/
|
||||||
|
public class Generator implements Runnable
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
if(mListener != null)
|
||||||
|
{
|
||||||
|
float[] samples = mOscillator.generate(mSamplesPerInterval);
|
||||||
|
mListener.receive(new ComplexBuffer(samples));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
180
src/source/tuner/test/TestTuner.java
Normal file
180
src/source/tuner/test/TestTuner.java
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* sdrtrunk
|
||||||
|
* Copyright (C) 2014-2017 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 source.tuner.test;
|
||||||
|
|
||||||
|
import dsp.filter.channelizer.PolyphaseChannelManager;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sample.Listener;
|
||||||
|
import sample.complex.ComplexBuffer;
|
||||||
|
import source.Source;
|
||||||
|
import source.SourceException;
|
||||||
|
import source.tuner.Tuner;
|
||||||
|
import source.tuner.TunerChannel;
|
||||||
|
import source.tuner.TunerChannelSource;
|
||||||
|
import source.tuner.TunerClass;
|
||||||
|
import source.tuner.TunerType;
|
||||||
|
|
||||||
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Testing tuner that implements an internal oscillator to output a unity gain tone at a specified frequency offset
|
||||||
|
* from a configurable center tune frequency and sample rate
|
||||||
|
*/
|
||||||
|
public class TestTuner extends Tuner
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(TestTuner.class);
|
||||||
|
private static int mInstanceCounter = 1;
|
||||||
|
private final int mInstanceID = mInstanceCounter++;
|
||||||
|
|
||||||
|
private PolyphaseChannelManager mPolyphaseChannelManager;
|
||||||
|
|
||||||
|
public TestTuner()
|
||||||
|
{
|
||||||
|
super("Test Tuner", new TestTunerController());
|
||||||
|
|
||||||
|
mLog.debug("Initializing test tuner. Tuner Controller's sample rate is:" + getTunerController().getSampleRate());
|
||||||
|
mLog.debug("Initializing test tuner. Test Tuner Controller's sample rate is:" + getTestTunerController().getSampleRate());
|
||||||
|
|
||||||
|
mPolyphaseChannelManager = new PolyphaseChannelManager(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the tuner controller cast as a test tuner controller.
|
||||||
|
*
|
||||||
|
* Note: this is a temporary method until the tuner controller interface can be updated with the start(listener)
|
||||||
|
* and stop() methods.
|
||||||
|
*/
|
||||||
|
private TestTunerController getTestTunerController()
|
||||||
|
{
|
||||||
|
return (TestTunerController)getTunerController();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the center tuned frequency for this tuner
|
||||||
|
* @param frequency
|
||||||
|
* @throws SourceException
|
||||||
|
*/
|
||||||
|
public void setFrequency(long frequency) throws SourceException
|
||||||
|
{
|
||||||
|
getTunerController().setFrequency(frequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToneFrequency(long frequency)
|
||||||
|
{
|
||||||
|
getTestTunerController().setToneFrequency(frequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getToneFrequency()
|
||||||
|
{
|
||||||
|
return getTestTunerController().getToneFrequency();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSampleRate(int sampleRate) throws SourceException
|
||||||
|
{
|
||||||
|
getTestTunerController().setSampleRate(sampleRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSampleRate()
|
||||||
|
{
|
||||||
|
return getTestTunerController().getSampleRate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUniqueID()
|
||||||
|
{
|
||||||
|
return getName() + "-" + mInstanceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TunerClass getTunerClass()
|
||||||
|
{
|
||||||
|
return TunerClass.TEST_TUNER;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TunerType getTunerType()
|
||||||
|
{
|
||||||
|
return TunerClass.TEST_TUNER.getTunerType();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getSampleSize()
|
||||||
|
{
|
||||||
|
return 16.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a polyphase channel source. The provided channel is a one-use channel that can be commanded to start
|
||||||
|
* and stop processing via the source event interface. Once stopped, the channel is deregistered from this tuner
|
||||||
|
* and is no longer usable -- a new channel source must be requested from the tuner.
|
||||||
|
*
|
||||||
|
* @param tunerChannel identifying the channel center frequency and bandwidth
|
||||||
|
* @return channel source or null if the channel source cannot be provided by this tuner
|
||||||
|
*/
|
||||||
|
public Source getChannelSource(TunerChannel tunerChannel)
|
||||||
|
{
|
||||||
|
return mPolyphaseChannelManager.getChannel(tunerChannel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TunerChannelSource getChannel(TunerChannel channel) throws RejectedExecutionException, SourceException
|
||||||
|
{
|
||||||
|
mLog.error("This method is deprecated in this test tuner implementation");
|
||||||
|
throw new IllegalStateException("Method deprecated - this test tuner cannot provide a TunerChannelSource");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void releaseChannel(TunerChannelSource source)
|
||||||
|
{
|
||||||
|
mLog.error("This method is deprecated in this test tuner implementation");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the listener to receive complex sample buffers and auto-starts the sample stream if this is the first
|
||||||
|
* registered listener.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addListener(Listener<ComplexBuffer> listener)
|
||||||
|
{
|
||||||
|
boolean isRunning = hasListeners();
|
||||||
|
|
||||||
|
super.addListener(listener);
|
||||||
|
|
||||||
|
if(!isRunning)
|
||||||
|
{
|
||||||
|
getTestTunerController().start(getSampleBroadcaster());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the listener from receiving complex sample buffers and auto-stops the sample stream when there are
|
||||||
|
* no more listeners.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void removeListener(Listener<ComplexBuffer> listener)
|
||||||
|
{
|
||||||
|
super.removeListener(listener);
|
||||||
|
|
||||||
|
if(!hasListeners())
|
||||||
|
{
|
||||||
|
getTestTunerController().stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
143
src/source/tuner/test/TestTunerController.java
Normal file
143
src/source/tuner/test/TestTunerController.java
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* sdrtrunk
|
||||||
|
* Copyright (C) 2014-2017 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 source.tuner.test;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sample.Listener;
|
||||||
|
import sample.complex.ComplexBuffer;
|
||||||
|
import source.SourceException;
|
||||||
|
import source.tuner.TunerController;
|
||||||
|
import source.tuner.configuration.TunerConfiguration;
|
||||||
|
|
||||||
|
public class TestTunerController extends TunerController
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(TestTunerController.class);
|
||||||
|
|
||||||
|
public static final long MINIMUM_FREQUENCY = 1000000l; //1 MHz
|
||||||
|
public static final long MAXIMUM_FREQUENCY = 10000000000l; //10 GHz
|
||||||
|
public static final long DEFAULT_FREQUENCY = 100000000; //100 MHz
|
||||||
|
public static final int DEFAULT_SAMPLE_RATE = 1000000; //1 MHz
|
||||||
|
public static final int DC_NOISE_BANDWIDTH = 5000; // +/-5 kHz
|
||||||
|
public static final double USABLE_BANDWIDTH = 0.90; //90% usable bandwidth - 5% unusable on either end
|
||||||
|
|
||||||
|
private SampleGenerator mSampleGenerator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tuner controller testing implementation.
|
||||||
|
*/
|
||||||
|
public TestTunerController()
|
||||||
|
{
|
||||||
|
super(MINIMUM_FREQUENCY, MAXIMUM_FREQUENCY, DC_NOISE_BANDWIDTH, USABLE_BANDWIDTH);
|
||||||
|
|
||||||
|
mSampleGenerator = new SampleGenerator(DEFAULT_SAMPLE_RATE, DEFAULT_FREQUENCY, 100);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mLog.debug("Setting frequency ...");
|
||||||
|
mFrequencyController.setFrequency(DEFAULT_FREQUENCY);
|
||||||
|
mLog.debug("Setting sample rate of freq controller to: " + DEFAULT_SAMPLE_RATE);
|
||||||
|
mFrequencyController.setSampleRate(DEFAULT_SAMPLE_RATE);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Error!", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the tuner controller producing samples that will be delivered to the specified listener
|
||||||
|
*/
|
||||||
|
public void start(Listener<ComplexBuffer> listener)
|
||||||
|
{
|
||||||
|
mSampleGenerator.setListener(listener);
|
||||||
|
mSampleGenerator.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the tuner controller sample generator and deregisters the listener
|
||||||
|
*/
|
||||||
|
public void stop()
|
||||||
|
{
|
||||||
|
mSampleGenerator.stop();
|
||||||
|
mSampleGenerator.setListener(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply(TunerConfiguration config) throws SourceException
|
||||||
|
{
|
||||||
|
mLog.error("Request to apply tuner configuration was ignored");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current center frequency for this tuner
|
||||||
|
* @throws SourceException
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long getTunedFrequency() throws SourceException
|
||||||
|
{
|
||||||
|
return mSampleGenerator.getFrequency();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the center frequency for this tuner
|
||||||
|
* @param frequency in hertz
|
||||||
|
* @throws SourceException
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setTunedFrequency(long frequency) throws SourceException
|
||||||
|
{
|
||||||
|
mSampleGenerator.setFrequency(frequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the tone frequency for the sample generator. The tone frequency is offset from the center tuned frequency.
|
||||||
|
* @param toneFrequency
|
||||||
|
*/
|
||||||
|
public void setToneFrequency(long toneFrequency)
|
||||||
|
{
|
||||||
|
mSampleGenerator.setFrequency(toneFrequency - getFrequency());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current tone frequency for this test controller.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public long getToneFrequency()
|
||||||
|
{
|
||||||
|
return getFrequency() + mSampleGenerator.getFrequency();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current sample rate for this tuner controller
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getCurrentSampleRate() throws SourceException
|
||||||
|
{
|
||||||
|
return mFrequencyController.getSampleRate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the sample rate for this tuner controller
|
||||||
|
*/
|
||||||
|
public void setSampleRate(int sampleRate) throws SourceException
|
||||||
|
{
|
||||||
|
mFrequencyController.setSampleRate(sampleRate);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user