#39 WIP polyphase channelizers. Creates test tuner and controller to

use in testing tuner and polyphase channel manager operations.
This commit is contained in:
Denny
2017-11-12 10:33:13 -05:00
parent a581da56a8
commit 5281679b7f
18 changed files with 1081 additions and 227 deletions

View File

@@ -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());
}
}
/**

View File

@@ -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.

View File

@@ -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();
}
});
}

View File

@@ -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

View File

@@ -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();
}
}
}
/**

View 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();
}
}
}

View File

@@ -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
*/

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}

View File

@@ -123,7 +123,7 @@ public abstract class MixerTuner extends Tuner implements Listener<ComplexBuffer
{
super.removeListener( listener );
if( mSampleListeners.isEmpty() )
if( !hasListeners() )
{
mComplexMixer.stop();
}

View File

@@ -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);
}
/**

View File

@@ -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;

View File

@@ -104,7 +104,7 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor
public int getSampleRate()
{
return mFrequencyController.getBandwidth();
return mFrequencyController.getSampleRate();
}
public double getFrequencyCorrection()

View File

@@ -32,6 +32,7 @@ public enum TunerType
RAFAELMICRO_R820T( "R820T" ),
RAFAELMICRO_R828D( "R828D" ),
RTL2832_VARIOUS( "Generic" ),
TEST("Test"),
UNKNOWN( "Unknown" );
private String mLabel;

View File

@@ -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
*/

View 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));
}
}
}
}

View 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();
}
}
}

View 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);
}
}