mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -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)
|
||||
{
|
||||
mChannels.add(polyphaseChannelSource);
|
||||
mSourceChangeBroadcaster.addListener(polyphaseChannelSource.getSourceEventListener());
|
||||
if(polyphaseChannelSource != null && !mChannels.contains(polyphaseChannelSource))
|
||||
{
|
||||
mChannels.add(polyphaseChannelSource);
|
||||
mSourceChangeBroadcaster.addListener(polyphaseChannelSource.getSourceEventListener());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,8 +119,11 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener<Co
|
||||
*/
|
||||
public void removeChannel(PolyphaseChannelSource polyphaseChannelSource)
|
||||
{
|
||||
mChannels.remove(polyphaseChannelSource);
|
||||
mSourceChangeBroadcaster.removeListener(polyphaseChannelSource.getSourceEventListener());
|
||||
if(polyphaseChannelSource != null && mChannels.contains(polyphaseChannelSource))
|
||||
{
|
||||
mChannels.remove(polyphaseChannelSource);
|
||||
mSourceChangeBroadcaster.removeListener(polyphaseChannelSource.getSourceEventListener());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -83,6 +83,17 @@ public class ChannelIndexCalculator
|
||||
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
|
||||
* is being used.
|
||||
|
||||
@@ -18,10 +18,6 @@
|
||||
******************************************************************************/
|
||||
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 org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -30,10 +26,11 @@ import sample.SampleType;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import settings.SettingsManager;
|
||||
import source.ISourceEventProcessor;
|
||||
import source.SourceEvent;
|
||||
import source.SourceException;
|
||||
import source.tuner.TunerChannel;
|
||||
import source.tuner.configuration.TunerConfigurationModel;
|
||||
import source.SourceEvent;
|
||||
import source.tuner.test.TestTuner;
|
||||
import spectrum.DFTProcessor;
|
||||
import spectrum.DFTSize;
|
||||
import spectrum.SpectrumPanel;
|
||||
@@ -44,9 +41,6 @@ import javax.swing.*;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import java.awt.*;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ChannelizerViewer extends JFrame
|
||||
{
|
||||
@@ -58,16 +52,16 @@ public class ChannelizerViewer extends JFrame
|
||||
private SettingsManager mSettingsManager = new SettingsManager(new TunerConfigurationModel());
|
||||
private JPanel mPrimaryPanel;
|
||||
private JPanel mControlPanel;
|
||||
private ChannelPanel mPrimarySpectrumPanel;
|
||||
private PrimarySpectrumPanel mPrimarySpectrumPanel;
|
||||
private ChannelArrayPanel mChannelPanel;
|
||||
private LowPhaseNoiseOscillator mOscillator;
|
||||
private int mChannelCount;
|
||||
private int mChannelsPerRow;
|
||||
private int mFilterTapsPerChannel;
|
||||
private int mSampleRate;
|
||||
private int mToneFrequency;
|
||||
private long mToneFrequency;
|
||||
private DFTSize mMainPanelDFTSize = DFTSize.FFT32768;
|
||||
private DFTSize mChannelPanelDFTSize = DFTSize.FFT00512;
|
||||
private TestTuner mTestTuner;
|
||||
|
||||
/**
|
||||
* GUI Test utility for researching polyphase channelizers.
|
||||
@@ -82,7 +76,13 @@ public class ChannelizerViewer extends JFrame
|
||||
mFilterTapsPerChannel = tapsPerChannel;
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -95,32 +95,6 @@ public class ChannelizerViewer extends JFrame
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||
setLocationRelativeTo(null);
|
||||
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()
|
||||
@@ -137,13 +111,14 @@ public class ChannelizerViewer extends JFrame
|
||||
return mPrimaryPanel;
|
||||
}
|
||||
|
||||
private ChannelPanel getSpectrumPanel()
|
||||
private PrimarySpectrumPanel getSpectrumPanel()
|
||||
{
|
||||
if(mPrimarySpectrumPanel == null)
|
||||
{
|
||||
// mPrimarySpectrumPanel = new ChannelPanel(mSettingsManager, mSampleRate);
|
||||
mPrimarySpectrumPanel = new PrimarySpectrumPanel(mSettingsManager, mSampleRate);
|
||||
mPrimarySpectrumPanel.setPreferredSize(new Dimension(1000, 200));
|
||||
mPrimarySpectrumPanel.setDFTSize(mMainPanelDFTSize);
|
||||
mTestTuner.addListener(mPrimarySpectrumPanel);
|
||||
}
|
||||
|
||||
return mPrimarySpectrumPanel;
|
||||
@@ -157,9 +132,11 @@ public class ChannelizerViewer extends JFrame
|
||||
mControlPanel.setLayout(new MigLayout("insets 0 0 0 0", "", ""));
|
||||
|
||||
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 );
|
||||
|
||||
model.addChangeListener(new ChangeListener()
|
||||
@@ -167,8 +144,8 @@ public class ChannelizerViewer extends JFrame
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e)
|
||||
{
|
||||
int frequency = model.getNumber().intValue();
|
||||
mOscillator.setFrequency(frequency);
|
||||
int toneFrequency = model.getNumber().intValue();
|
||||
mTestTuner.setToneFrequency(toneFrequency);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -190,35 +167,21 @@ public class ChannelizerViewer extends JFrame
|
||||
{
|
||||
if(mChannelPanel == null)
|
||||
{
|
||||
float[] taps = null;
|
||||
|
||||
try
|
||||
{
|
||||
taps = getFilter();
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
{
|
||||
mLog.debug("Couldn't design filter", fde);
|
||||
}
|
||||
|
||||
mChannelPanel = new ChannelArrayPanel(taps);
|
||||
mChannelPanel = new ChannelArrayPanel();
|
||||
}
|
||||
|
||||
return mChannelPanel;
|
||||
}
|
||||
|
||||
private float[] getFilter() throws FilterDesignException
|
||||
{
|
||||
return FilterFactory.getSincChannelizer(CHANNEL_BANDWIDTH, mChannelCount, mFilterTapsPerChannel,
|
||||
Window.WindowType.BLACKMAN_HARRIS_7, true);
|
||||
}
|
||||
// private float[] getFilter() throws FilterDesignException
|
||||
// {
|
||||
// return FilterFactory.getSincChannelizer(CHANNEL_BANDWIDTH, mChannelCount, mFilterTapsPerChannel,
|
||||
// Window.WindowType.BLACKMAN_HARRIS_7, true);
|
||||
// }
|
||||
|
||||
public class ChannelArrayPanel extends JPanel implements Listener<ComplexBuffer>
|
||||
public class ChannelArrayPanel extends JPanel
|
||||
{
|
||||
private ComplexPolyphaseChannelizer mPolyphaseChannelizer;
|
||||
private ChannelDistributor mChannelDistributor;
|
||||
|
||||
public ChannelArrayPanel(float[] taps)
|
||||
public ChannelArrayPanel()
|
||||
{
|
||||
int bufferSize = CHANNEL_BANDWIDTH / CHANNEL_FFT_FRAME_RATE;
|
||||
if(bufferSize % 2 == 1)
|
||||
@@ -226,10 +189,6 @@ public class ChannelizerViewer extends JFrame
|
||||
bufferSize++;
|
||||
}
|
||||
|
||||
mPolyphaseChannelizer = new ComplexPolyphaseChannelizer(taps, mChannelCount, CHANNEL_BANDWIDTH);
|
||||
mChannelDistributor = new ChannelDistributor(bufferSize, mChannelCount);
|
||||
// mPolyphaseChannelizer.setChannelDistributor(mChannelDistributor);
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
@@ -243,7 +202,8 @@ public class ChannelizerViewer extends JFrame
|
||||
for(int x = 0; x < mChannelCount; x++)
|
||||
{
|
||||
//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.setDFTSize(mChannelPanelDFTSize);
|
||||
|
||||
@@ -255,28 +215,17 @@ public class ChannelizerViewer extends JFrame
|
||||
{
|
||||
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 ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter();
|
||||
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]"));
|
||||
mSpectrumPanel = new SpectrumPanel(settingsManager);
|
||||
@@ -285,16 +234,6 @@ public class ChannelizerViewer extends JFrame
|
||||
mDFTProcessor.addConverter(mComplexDecibelConverter);
|
||||
mDFTProcessor.process(SourceEvent.sampleRateChange(sampleRate));
|
||||
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)
|
||||
@@ -315,12 +254,57 @@ public class ChannelizerViewer extends JFrame
|
||||
}
|
||||
}
|
||||
|
||||
public class SampleGenerationTask implements Runnable
|
||||
public class ChannelPanel extends JPanel implements Listener<ComplexBuffer>, ISourceEventProcessor
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
private PolyphaseChannelSource mSource;
|
||||
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()
|
||||
{
|
||||
frame.setVisible(true);
|
||||
frame.start();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -21,11 +21,11 @@ package dsp.filter.channelizer;
|
||||
import buffer.ReverseFloatCircularBuffer;
|
||||
import dsp.filter.FilterFactory;
|
||||
import dsp.filter.Window;
|
||||
import dsp.filter.design.FilterDesignException;
|
||||
import dsp.mixer.Oscillator;
|
||||
import org.jtransforms.fft.FloatFFT_1D;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sample.complex.ComplexBuffer;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
@@ -33,6 +33,8 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ComplexPolyphaseChannelizerM2.class);
|
||||
|
||||
private static final int DEFAULT_CHANNEL_BANDWIDTH = 12500;
|
||||
|
||||
private ReverseFloatCircularBuffer[] mDataBuffers;
|
||||
private float[][] mFilterCoefficients;
|
||||
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
|
||||
* 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
|
||||
*
|
||||
* @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).
|
||||
*
|
||||
* @param channelSampleRate for each decimated, oversampled channel
|
||||
*/
|
||||
public ComplexPolyphaseChannelizerM2(float[] taps, int channels, int channelSampleRate)
|
||||
@@ -64,6 +68,72 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
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)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -21,6 +21,7 @@ package dsp.filter.channelizer;
|
||||
import dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor;
|
||||
import dsp.filter.channelizer.output.OneChannelOutputProcessor;
|
||||
import dsp.filter.channelizer.output.TwoChannelOutputProcessor;
|
||||
import dsp.filter.design.FilterDesignException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sample.Listener;
|
||||
@@ -34,28 +35,29 @@ import source.tuner.TunerChannel;
|
||||
import java.util.List;
|
||||
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 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 int CHANNEL_SAMPLE_RATE = (int)(CHANNEL_BANDWIDTH * CHANNEL_OVERSAMPLING);
|
||||
|
||||
private Tuner mTuner;
|
||||
private List<PolyphaseChannelSource> mChannelSources = new CopyOnWriteArrayList<>();
|
||||
private ChannelIndexCalculator mChannelIndexCalculator;
|
||||
private ComplexPolyphaseChannelizerM2 mPolyphaseChannelizer;
|
||||
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
|
||||
* sources and wraps a polyphase channelizer processing sample buffers produced by the tuner and distributing
|
||||
* channelized sample buffers to each allocated DDC polyphase channel source. This class is responsible for
|
||||
* monitoring the tuner for changes in center frequency and/or sample rate and updating active DDC polyphase channel
|
||||
* sources accordingly. This class also monitors source event requests and notifications received from active DDC
|
||||
* polyphase channel sources to adjust sample streams as required.
|
||||
* Polyphase Channel Manager is a DDC channel manager and complex buffer queue/processor for a tuner. This class
|
||||
* provides DDC polyphase channel sources and wraps a polyphase channelizer processing sample buffers produced by
|
||||
* the tuner and distributing channelized sample buffers to each allocated DDC polyphase channel source. This
|
||||
* class is responsible for monitoring the tuner for changes in center frequency and/or sample rate and updating
|
||||
* active DDC polyphase channel sources accordingly. This class also monitors source event requests and
|
||||
* 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)
|
||||
{
|
||||
@@ -64,25 +66,29 @@ public class PolyphaseChannelManager implements ISourceEventProcessor, Listener<
|
||||
throw new IllegalArgumentException("Tuner argument cannot be null");
|
||||
}
|
||||
|
||||
mTuner = tuner;
|
||||
|
||||
int sampleRate = mTuner.getTunerController().getSampleRate();
|
||||
|
||||
//Ensure that tuner sample rate is a multiple of channel sample rate since polyphase channelizer is M2
|
||||
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");
|
||||
}
|
||||
|
||||
mTuner = tuner;
|
||||
|
||||
int channelCount = sampleRate / CHANNEL_BANDWIDTH;
|
||||
|
||||
long frequency = mTuner.getTunerController().getFrequency();
|
||||
int channelCount = sampleRate / CHANNEL_BANDWIDTH;
|
||||
|
||||
mChannelIndexCalculator = new ChannelIndexCalculator(frequency, channelCount, CHANNEL_BANDWIDTH,
|
||||
CHANNEL_OVERSAMPLING);
|
||||
|
||||
mBufferProcessor = new ScheduledBufferProcessor(500, 100, 100, 50);
|
||||
|
||||
updateChannelizer(sampleRate, null);
|
||||
|
||||
mTuner.addSourceEventListener(this);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,7 +109,9 @@ public class PolyphaseChannelManager implements ISourceEventProcessor, Listener<
|
||||
|
||||
if(outputProcessor != null)
|
||||
{
|
||||
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, this, CHANNEL_SAMPLE_RATE);
|
||||
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
|
||||
CHANNEL_SAMPLE_RATE);
|
||||
|
||||
mChannelSources.add(channelSource);
|
||||
}
|
||||
}
|
||||
@@ -117,7 +125,13 @@ public class PolyphaseChannelManager implements ISourceEventProcessor, Listener<
|
||||
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())
|
||||
{
|
||||
@@ -127,6 +141,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor, Listener<
|
||||
return new TwoChannelOutputProcessor(CHANNEL_SAMPLE_RATE, indexes);
|
||||
default:
|
||||
//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;
|
||||
}
|
||||
}
|
||||
@@ -139,11 +154,15 @@ public class PolyphaseChannelManager implements ISourceEventProcessor, Listener<
|
||||
*/
|
||||
private void startChannelSource(PolyphaseChannelSource channelSource)
|
||||
{
|
||||
mPolyphaseChannelizer.addChannel(channelSource);
|
||||
|
||||
if(mPolyphaseChannelizer.getRegisteredChannelCount() == 1)
|
||||
synchronized(mBufferProcessor)
|
||||
{
|
||||
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)
|
||||
{
|
||||
mChannelSources.remove(channelSource);
|
||||
|
||||
if(mPolyphaseChannelizer.getRegisteredChannelCount() == 0)
|
||||
synchronized(mBufferProcessor)
|
||||
{
|
||||
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
|
||||
* channel sources.
|
||||
*/
|
||||
@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.
|
||||
* Implements the Listener<SourceEvent> interface for receiving notifications of frequency and sample rate
|
||||
* changes from the tuner.
|
||||
*/
|
||||
@Override
|
||||
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 float THREE_HALVES = 3.0f / 2.0f;
|
||||
private int mSampleRate;
|
||||
private int mFrequency;
|
||||
private long mFrequency;
|
||||
private float mInphase = 1.0f;
|
||||
private float mQuadrature = 0.0f;
|
||||
private float mPreviousInphase = 1.0f;
|
||||
@@ -46,7 +46,7 @@ public class LowPhaseNoiseOscillator
|
||||
* @param sampleRate for the oscillator
|
||||
* @param frequency to generate
|
||||
*/
|
||||
public LowPhaseNoiseOscillator(int sampleRate, int frequency)
|
||||
public LowPhaseNoiseOscillator(int sampleRate, long frequency)
|
||||
{
|
||||
mSampleRate = sampleRate;
|
||||
mFrequency = frequency;
|
||||
@@ -54,18 +54,40 @@ public class LowPhaseNoiseOscillator
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sample rate for this oscillator
|
||||
*/
|
||||
public void setSampleRate(int sampleRate)
|
||||
{
|
||||
mSampleRate = sampleRate;
|
||||
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;
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Current tone frequency for this oscillator
|
||||
*/
|
||||
public long getFrequency()
|
||||
{
|
||||
return mFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the internal values after a frequency or sample rate change
|
||||
*/
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
@@ -25,62 +25,100 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
|
||||
public void removeListener( Listener<T> listener )
|
||||
{
|
||||
mListeners.remove( listener );
|
||||
}
|
||||
|
||||
public void clear()
|
||||
{
|
||||
mListeners.clear();
|
||||
}
|
||||
|
||||
public void broadcast( T t )
|
||||
/**
|
||||
* Clear listeners to prepare for garbage collection
|
||||
*/
|
||||
public void dispose()
|
||||
{
|
||||
for( Listener<T> listener: mListeners )
|
||||
{
|
||||
listener.receive( t );
|
||||
}
|
||||
mListeners.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
@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 );
|
||||
|
||||
if( mSampleListeners.isEmpty() )
|
||||
if( !hasListeners() )
|
||||
{
|
||||
mComplexMixer.stop();
|
||||
}
|
||||
|
||||
@@ -41,11 +41,7 @@ public abstract class Tuner implements ITunerChannelProvider
|
||||
|
||||
private String mName;
|
||||
private TunerController mTunerController;
|
||||
/**
|
||||
* 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<>();
|
||||
private Broadcaster<ComplexBuffer> mSampleBroadcaster = new Broadcaster<>();
|
||||
protected List<Listener<TunerEvent>> mTunerChangeListeners = new ArrayList<>();
|
||||
protected Broadcaster<SourceEvent> mSourceEventBroadcaster = new Broadcaster<>();
|
||||
|
||||
@@ -89,7 +85,7 @@ public abstract class Tuner implements ITunerChannelProvider
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mSampleListeners.clear();
|
||||
mSampleBroadcaster.clear();
|
||||
}
|
||||
|
||||
public void setName(String name)
|
||||
@@ -161,7 +157,7 @@ public abstract class Tuner implements ITunerChannelProvider
|
||||
*/
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
for(Listener<ComplexBuffer> listener : mSampleListeners)
|
||||
{
|
||||
listener.receive(sampleBuffer);
|
||||
}
|
||||
mSampleBroadcaster.broadcast(sampleBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -51,7 +51,8 @@ public enum TunerClass
|
||||
TERRATEC_NOXON_REV2( TunerType.ELONICS_E4000, "0CCD", "00E0", "Terratec", "NOXON R2" ),
|
||||
TERRATEC_T_STICK_PLUS( TunerType.ELONICS_E4000, "0CCD", "00D7", "Terratec", "T Stick Plus" ),
|
||||
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" );
|
||||
|
||||
private TunerType mTunerType;
|
||||
|
||||
@@ -104,7 +104,7 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor
|
||||
|
||||
public int getSampleRate()
|
||||
{
|
||||
return mFrequencyController.getBandwidth();
|
||||
return mFrequencyController.getSampleRate();
|
||||
}
|
||||
|
||||
public double getFrequencyCorrection()
|
||||
|
||||
@@ -32,6 +32,7 @@ public enum TunerType
|
||||
RAFAELMICRO_R820T( "R820T" ),
|
||||
RAFAELMICRO_R828D( "R828D" ),
|
||||
RTL2832_VARIOUS( "Generic" ),
|
||||
TEST("Test"),
|
||||
UNKNOWN( "Unknown" );
|
||||
|
||||
private String mLabel;
|
||||
|
||||
@@ -50,7 +50,7 @@ public class FrequencyController
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bandwidth/sample rate in hertz
|
||||
* Get bandwidth in hertz
|
||||
*/
|
||||
public int getBandwidth()
|
||||
{
|
||||
@@ -67,6 +67,14 @@ public class FrequencyController
|
||||
broadcastSampleRateChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sample rate in hertz
|
||||
*/
|
||||
public int getSampleRate()
|
||||
{
|
||||
return mSampleRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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