#39 WIP polyphase channelizers.

This commit is contained in:
Denny
2017-11-18 05:50:00 -05:00
parent 5281679b7f
commit b3e4bd6a9c
16 changed files with 217 additions and 90 deletions

View File

@@ -834,9 +834,10 @@ public class FilterFactory
if(logResults) if(logResults)
{ {
mLog.debug("Polyphase TunerChannelizer Filter Design Summary"); mLog.debug("Polyphase TunerChannelizer Filter Design Summary");
mLog.debug("-------------------------------------------"); mLog.debug("-----------------------------------------------------");
mLog.debug("Window Type: " + windowType.name());
mLog.debug("Channels: " + channels); mLog.debug("Channels: " + channels);
mLog.debug("Taps Per Channel: " + tapsPerChannel); mLog.debug("Taps Per Channel: " + tapsPerChannel + " (Requested:" + tapsPerChannel + ")");
mLog.debug("Filter Length: " + (taps.length + 1)); mLog.debug("Filter Length: " + (taps.length + 1));
mLog.debug("Channel Bandwidth: " + channelBandwidth); mLog.debug("Channel Bandwidth: " + channelBandwidth);
mLog.debug("Cutoff Frequency: " + (sampleRate * cutoffFrequency)); mLog.debug("Cutoff Frequency: " + (sampleRate * cutoffFrequency));

View File

@@ -26,6 +26,7 @@ import sample.complex.ComplexBuffer;
import source.ISourceEventListener; import source.ISourceEventListener;
import source.SourceEvent; import source.SourceEvent;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;

View File

@@ -187,6 +187,9 @@ public class ChannelIndexCalculator
*/ */
public List<Integer> getPolyphaseChannelIndexes(TunerChannel tunerChannel) throws IllegalArgumentException public List<Integer> getPolyphaseChannelIndexes(TunerChannel tunerChannel) throws IllegalArgumentException
{ {
long min = getMinimumFrequency();
long max = getMaximumFrequency();
if(tunerChannel.getMinFrequency() < getMinimumFrequency() || if(tunerChannel.getMinFrequency() < getMinimumFrequency() ||
tunerChannel.getMaxFrequency() > getMaximumFrequency()) tunerChannel.getMaxFrequency() > getMaximumFrequency())
{ {

View File

@@ -32,6 +32,7 @@ import source.tuner.TunerChannel;
import source.tuner.configuration.TunerConfigurationModel; import source.tuner.configuration.TunerConfigurationModel;
import source.tuner.test.TestTuner; import source.tuner.test.TestTuner;
import spectrum.DFTProcessor; import spectrum.DFTProcessor;
import spectrum.DFTResultsListener;
import spectrum.DFTSize; import spectrum.DFTSize;
import spectrum.SpectrumPanel; import spectrum.SpectrumPanel;
import spectrum.converter.ComplexDecibelConverter; import spectrum.converter.ComplexDecibelConverter;
@@ -56,8 +57,6 @@ public class ChannelizerViewer extends JFrame
private ChannelArrayPanel mChannelPanel; private ChannelArrayPanel mChannelPanel;
private int mChannelCount; private int mChannelCount;
private int mChannelsPerRow; private int mChannelsPerRow;
private int mFilterTapsPerChannel;
private int mSampleRate;
private long mToneFrequency; private long mToneFrequency;
private DFTSize mMainPanelDFTSize = DFTSize.FFT32768; private DFTSize mMainPanelDFTSize = DFTSize.FFT32768;
private DFTSize mChannelPanelDFTSize = DFTSize.FFT00512; private DFTSize mChannelPanelDFTSize = DFTSize.FFT00512;
@@ -66,19 +65,15 @@ public class ChannelizerViewer extends JFrame
/** /**
* GUI Test utility for researching polyphase channelizers. * GUI Test utility for researching polyphase channelizers.
* *
* @param channelCount
* @param channelsPerRow * @param channelsPerRow
*/ */
public ChannelizerViewer(int channelCount, int channelsPerRow, int tapsPerChannel) public ChannelizerViewer(int channelsPerRow)
{ {
mChannelCount = channelCount;
mChannelsPerRow = channelsPerRow;
mFilterTapsPerChannel = tapsPerChannel;
mSampleRate = mChannelCount * CHANNEL_BANDWIDTH;
mTestTuner = new TestTuner(); mTestTuner = new TestTuner();
mChannelCount = mTestTuner.getSampleRate() / CHANNEL_BANDWIDTH;
mChannelsPerRow = channelsPerRow;
mToneFrequency = mTestTuner.getTunerController().getFrequency() + mToneFrequency = mTestTuner.getTunerController().getFrequency() +
(int)(CHANNEL_BANDWIDTH * 1); //Set to channel 1 as default (int)(CHANNEL_BANDWIDTH * 1); //Set to channel 1 as default
@@ -115,7 +110,7 @@ public class ChannelizerViewer extends JFrame
{ {
if(mPrimarySpectrumPanel == null) if(mPrimarySpectrumPanel == null)
{ {
mPrimarySpectrumPanel = new PrimarySpectrumPanel(mSettingsManager, mSampleRate); mPrimarySpectrumPanel = new PrimarySpectrumPanel(mSettingsManager, mTestTuner.getSampleRate());
mPrimarySpectrumPanel.setPreferredSize(new Dimension(1000, 200)); mPrimarySpectrumPanel.setPreferredSize(new Dimension(1000, 200));
mPrimarySpectrumPanel.setDFTSize(mMainPanelDFTSize); mPrimarySpectrumPanel.setDFTSize(mMainPanelDFTSize);
mTestTuner.addListener(mPrimarySpectrumPanel); mTestTuner.addListener(mPrimarySpectrumPanel);
@@ -132,7 +127,7 @@ public class ChannelizerViewer extends JFrame
mControlPanel.setLayout(new MigLayout("insets 0 0 0 0", "", "")); mControlPanel.setLayout(new MigLayout("insets 0 0 0 0", "", ""));
mControlPanel.add(new JLabel("Tone:"), "align left"); mControlPanel.add(new JLabel("Tone:"), "align left");
int maximumFrequencyOffset = mSampleRate / 2; int maximumFrequencyOffset = mTestTuner.getSampleRate() / 2;
long minimumFrequency = mTestTuner.getTunerController().getFrequency() - maximumFrequencyOffset; long minimumFrequency = mTestTuner.getTunerController().getFrequency() - maximumFrequencyOffset;
long maximumFrequency = mTestTuner.getTunerController().getFrequency() + maximumFrequencyOffset; long maximumFrequency = mTestTuner.getTunerController().getFrequency() + maximumFrequencyOffset;
@@ -154,9 +149,9 @@ public class ChannelizerViewer extends JFrame
mControlPanel.add(spinner); mControlPanel.add(spinner);
mControlPanel.add(new JLabel("Hz")); mControlPanel.add(new JLabel("Hz"));
mControlPanel.add(new JLabel("Sample Rate: " + mSampleRate + " Hz"), "push,align right"); mControlPanel.add(new JLabel("Sample Rate: " + mTestTuner.getSampleRate() + " Hz"), "push,align right");
mControlPanel.add(new JLabel("Max: " + (mSampleRate / 2) + " Hz"), "push,align right"); mControlPanel.add(new JLabel("Max: " + (mTestTuner.getSampleRate() / 2) + " Hz"), "push,align right");
mControlPanel.add(new JLabel("Channels: " + mChannelCount), "push,align right"); mControlPanel.add(new JLabel("Channels: " + mChannelCount), "push,align right");
} }
@@ -196,15 +191,20 @@ public class ChannelizerViewer extends JFrame
{ {
setLayout(new MigLayout("insets 0 0 0 0", "fill", "fill")); setLayout(new MigLayout("insets 0 0 0 0", "fill", "fill"));
long spectralBandwidth = mChannelCount * CHANNEL_BANDWIDTH; long spectralBandwidth = mTestTuner.getSampleRate();
long halfSpectralBandwidth = spectralBandwidth / 2; long halfSpectralBandwidth = spectralBandwidth / 2;
int channelToLog = -1;
for(int x = 0; x < mChannelCount; x++) for(int x = 0; x < mChannelCount; x++)
{ {
//place the channels left and right of 10.0 MHz //place the channels left and right of 10.0 MHz
long frequency = mTestTuner.getTunerController().getFrequency() + long frequency = mTestTuner.getTunerController().getFrequency() +
((x * CHANNEL_BANDWIDTH) - halfSpectralBandwidth); ((x * CHANNEL_BANDWIDTH) - halfSpectralBandwidth) + (CHANNEL_BANDWIDTH / 2);
ChannelPanel channelPanel = new ChannelPanel(mSettingsManager, CHANNEL_BANDWIDTH * 2, frequency, CHANNEL_BANDWIDTH);
mLog.debug("Channel " + x + " Frequency: " + frequency);
ChannelPanel channelPanel = new ChannelPanel(mSettingsManager, CHANNEL_BANDWIDTH * 2, frequency, CHANNEL_BANDWIDTH, (x == channelToLog));
channelPanel.setDFTSize(mChannelPanelDFTSize); channelPanel.setDFTSize(mChannelPanelDFTSize);
if(x % mChannelsPerRow == mChannelsPerRow - 1) if(x % mChannelsPerRow == mChannelsPerRow - 1)
@@ -260,8 +260,9 @@ public class ChannelizerViewer extends JFrame
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX); private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter(); private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter();
private SpectrumPanel mSpectrumPanel; private SpectrumPanel mSpectrumPanel;
private long mFrequency;
public ChannelPanel(SettingsManager settingsManager, int sampleRate, long frequency, int bandwidth) public ChannelPanel(SettingsManager settingsManager, int sampleRate, long frequency, int bandwidth, boolean enableLogging)
{ {
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]")); setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
mSpectrumPanel = new SpectrumPanel(settingsManager); mSpectrumPanel = new SpectrumPanel(settingsManager);
@@ -269,14 +270,32 @@ public class ChannelizerViewer extends JFrame
mDFTProcessor.addConverter(mComplexDecibelConverter); mDFTProcessor.addConverter(mComplexDecibelConverter);
mDFTProcessor.process(SourceEvent.sampleRateChange(sampleRate)); mDFTProcessor.process(SourceEvent.sampleRateChange(sampleRate));
mComplexDecibelConverter.addListener(mSpectrumPanel); mComplexDecibelConverter.addListener(mSpectrumPanel);
mFrequency = frequency;
TunerChannel tunerChannel = new TunerChannel(frequency, bandwidth); TunerChannel tunerChannel = new TunerChannel(frequency, bandwidth);
mSource = (PolyphaseChannelSource)mTestTuner.getChannelSource(tunerChannel); mSource = (PolyphaseChannelSource)mTestTuner.getChannelSource(tunerChannel);
if(mSource != null) if(mSource != null)
{ {
mSource.setListener(mDFTProcessor); if(enableLogging)
{
mSource.setListener(new Listener<ComplexBuffer>()
{
@Override
public void receive(ComplexBuffer complexBuffer)
{
mLog.debug("Sending samples to DFT processor: " + complexBuffer.getSamples().length);
mDFTProcessor.receive(complexBuffer);
}
});
}
else
{
mSource.setListener(mDFTProcessor);
}
mSource.start(ThreadPool.SCHEDULED); mSource.start(ThreadPool.SCHEDULED);
} }
else else
@@ -298,6 +317,7 @@ public class ChannelizerViewer extends JFrame
@Override @Override
public void receive(ComplexBuffer complexBuffer) public void receive(ComplexBuffer complexBuffer)
{ {
mLog.debug("Got a buffer for the channel dft processor");
mDFTProcessor.receive(complexBuffer); mDFTProcessor.receive(complexBuffer);
} }
@@ -310,11 +330,9 @@ public class ChannelizerViewer extends JFrame
public static void main(String[] args) public static void main(String[] args)
{ {
int channelCount = 160; int channelsPerRow = 4;
int channelsPerRow = 20;
int tapsPerChannel = 17;
final ChannelizerViewer frame = new ChannelizerViewer(channelCount, channelsPerRow, tapsPerChannel); final ChannelizerViewer frame = new ChannelizerViewer(channelsPerRow);
EventQueue.invokeLater(new Runnable() EventQueue.invokeLater(new Runnable()
{ {

View File

@@ -77,14 +77,14 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
* *
* @param sampleRate to be channelized. * @param sampleRate to be channelized.
*/ */
private ComplexPolyphaseChannelizerM2(int sampleRate) throws FilterDesignException private ComplexPolyphaseChannelizerM2(int sampleRate, int tapsPerFilter) throws FilterDesignException
{ {
super(sampleRate / DEFAULT_CHANNEL_BANDWIDTH, DEFAULT_CHANNEL_BANDWIDTH); super(sampleRate / DEFAULT_CHANNEL_BANDWIDTH, DEFAULT_CHANNEL_BANDWIDTH);
mLog.debug("Sample Rate: " + sampleRate); mLog.debug("Sample Rate: " + sampleRate);
float[] filterTaps = FilterFactory.getSincChannelizer(getChannelSampleRate(), getChannelCount(), float[] filterTaps = FilterFactory.getSincChannelizer(getChannelSampleRate(), getChannelCount(),
15, Window.WindowType.BLACKMAN_HARRIS_7, true); tapsPerFilter, Window.WindowType.BLACKMAN_HARRIS_7, true);
initFilters(filterTaps); initFilters(filterTaps);
} }
@@ -97,7 +97,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
* @return channelizer * @return channelizer
* @throws IllegalArgumentException if the sample rate argument is not an even multiple of 25.0 kHz * @throws IllegalArgumentException if the sample rate argument is not an even multiple of 25.0 kHz
*/ */
public static ComplexPolyphaseChannelizerM2 create(int sampleRate) throws IllegalArgumentException, public static ComplexPolyphaseChannelizerM2 create(int sampleRate, int tapsPerFilter) throws IllegalArgumentException,
FilterDesignException FilterDesignException
{ {
if(sampleRate % (2 * DEFAULT_CHANNEL_BANDWIDTH) != 0) if(sampleRate % (2 * DEFAULT_CHANNEL_BANDWIDTH) != 0)
@@ -105,7 +105,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
throw new IllegalArgumentException("Sample rate must be an even multiple of 25 kHz"); throw new IllegalArgumentException("Sample rate must be an even multiple of 25 kHz");
} }
return new ComplexPolyphaseChannelizerM2(sampleRate); return new ComplexPolyphaseChannelizerM2(sampleRate, tapsPerFilter);
} }
/** /**

View File

@@ -41,6 +41,7 @@ public class PolyphaseChannelManager implements Listener<SourceEvent>
private static final int CHANNEL_BANDWIDTH = 12500; private static final int CHANNEL_BANDWIDTH = 12500;
private static final double CHANNEL_OVERSAMPLING = 2.0; private static final double CHANNEL_OVERSAMPLING = 2.0;
private static final int CHANNEL_SAMPLE_RATE = (int)(CHANNEL_BANDWIDTH * CHANNEL_OVERSAMPLING); private static final int CHANNEL_SAMPLE_RATE = (int)(CHANNEL_BANDWIDTH * CHANNEL_OVERSAMPLING);
private static final int POLYPHASE_FILTER_TAPS_PER_CHANNEL = 17;
private Tuner mTuner; private Tuner mTuner;
private List<PolyphaseChannelSource> mChannelSources = new CopyOnWriteArrayList<>(); private List<PolyphaseChannelSource> mChannelSources = new CopyOnWriteArrayList<>();
@@ -232,7 +233,9 @@ public class PolyphaseChannelManager implements Listener<SourceEvent>
{ {
if(mPolyphaseChannelizer == null) if(mPolyphaseChannelizer == null)
{ {
mPolyphaseChannelizer = ComplexPolyphaseChannelizerM2.create(sampleRate); mPolyphaseChannelizer = ComplexPolyphaseChannelizerM2.create(sampleRate,
POLYPHASE_FILTER_TAPS_PER_CHANNEL);
mBufferProcessor.setListener(mPolyphaseChannelizer); mBufferProcessor.setListener(mPolyphaseChannelizer);
} }
else else

View File

@@ -32,6 +32,7 @@ import source.SourceEvent;
import source.SourceException; import source.SourceException;
import source.tuner.TunerChannel; import source.tuner.TunerChannel;
import java.util.Arrays;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -67,15 +68,29 @@ public class PolyphaseChannelSource extends ComplexSource implements ISourceEven
mPolyphaseChannelOutputProcessor = outputProcessor; mPolyphaseChannelOutputProcessor = outputProcessor;
mUpstreamSourceEventHandler = new UpstreamSourceEventHandler(externalEventListener);; mUpstreamSourceEventHandler = new UpstreamSourceEventHandler(externalEventListener);;
mChannelSampleRate = channelSampleRate; mChannelSampleRate = channelSampleRate;
} }
/** /**
* Registers the listener to receive complex sample buffers from this channel source * Registers the listener to receive complex sample buffers from this channel source
*/ */
@Override @Override
public void setListener(Listener<ComplexBuffer> listener) public void setListener(final Listener<ComplexBuffer> listener)
{ {
mComplexBufferAssembler.setListener(listener); // mComplexBufferAssembler.setListener(listener);
mComplexBufferAssembler.setListener(new Listener<ComplexBuffer>()
{
@Override
public void receive(ComplexBuffer complexBuffer)
{
if(mTunerChannel.getFrequency() == 99981250)
{
// mLog.debug(Arrays.toString(complexBuffer.getSamples()));
}
listener.receive(complexBuffer);
}
});
} }
/** /**

View File

@@ -18,6 +18,8 @@
******************************************************************************/ ******************************************************************************/
package dsp.filter.channelizer; package dsp.filter.channelizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener; import sample.Listener;
import sample.OverflowableTransferQueue; import sample.OverflowableTransferQueue;
import sample.real.IOverflowListener; import sample.real.IOverflowListener;
@@ -31,6 +33,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class ScheduledBufferProcessor<E> implements Listener<E> public class ScheduledBufferProcessor<E> implements Listener<E>
{ {
private final static Logger mLog = LoggerFactory.getLogger(ScheduledBufferProcessor.class);
private OverflowableTransferQueue<E> mQueue; private OverflowableTransferQueue<E> mQueue;
private Listener<E> mListener; private Listener<E> mListener;
private ScheduledFuture<?> mScheduledFuture; private ScheduledFuture<?> mScheduledFuture;
@@ -59,6 +63,15 @@ public class ScheduledBufferProcessor<E> implements Listener<E>
public ScheduledBufferProcessor(int maximumSize, int resetThreshold, long distributionInterval, int maxBuffersPerInterval) public ScheduledBufferProcessor(int maximumSize, int resetThreshold, long distributionInterval, int maxBuffersPerInterval)
{ {
mQueue = new OverflowableTransferQueue<>(maximumSize, resetThreshold); mQueue = new OverflowableTransferQueue<>(maximumSize, resetThreshold);
mQueue.setOverflowListener(new IOverflowListener()
{
@Override
public void sourceOverflow(boolean overflow)
{
mLog.error("Overflow state changed - overflow:" + overflow);
}
});
mDistributionInterval = distributionInterval; mDistributionInterval = distributionInterval;
mMaxBuffersPerInterval = maxBuffersPerInterval; mMaxBuffersPerInterval = maxBuffersPerInterval;
} }

View File

@@ -33,14 +33,14 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
{ {
private final static Logger mLog = LoggerFactory.getLogger(ChannelOutputProcessor.class); private final static Logger mLog = LoggerFactory.getLogger(ChannelOutputProcessor.class);
private static final int OVERFLOW_THRESHOLD = 25000 * 3; //25 kHz * 3 seconds
private static final int RESET_THRESHOLD = (int)(OVERFLOW_THRESHOLD * .25); //reset at 25%
private OverflowableTransferQueue<float[]> mChannelResultsQueue; private OverflowableTransferQueue<float[]> mChannelResultsQueue;
private List<float[]> mChannelResultsToProcess = new ArrayList<>(); private List<float[]> mChannelResultsToProcess = new ArrayList<>();
private int mMaxResultsToProcess;
private int mInputChannelCount; private int mInputChannelCount;
//TODO: swap this out and use the LowPhaseNoiseOscillator
private Oscillator mFrequencyCorrectionMixer; private Oscillator mFrequencyCorrectionMixer;
private boolean mFrequencyCorrectionEnabled;
/** /**
* Base class for polyphase channelizer output channel processing. Provides built-in frequency translation * Base class for polyphase channelizer output channel processing. Provides built-in frequency translation
@@ -54,7 +54,9 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
{ {
mInputChannelCount = inputChannelCount; mInputChannelCount = inputChannelCount;
mFrequencyCorrectionMixer = new Oscillator(0, sampleRate); mFrequencyCorrectionMixer = new Oscillator(0, sampleRate);
mChannelResultsQueue = new OverflowableTransferQueue<>(OVERFLOW_THRESHOLD, RESET_THRESHOLD); mMaxResultsToProcess = sampleRate / 10 * 2; //process at 100 millis interval, twice the expected inflow rate
mChannelResultsQueue = new OverflowableTransferQueue<>(sampleRate * 3, (int)(sampleRate * 0.5));
} }
@Override @Override
@@ -72,33 +74,27 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
{ {
try try
{ {
mChannelResultsQueue.drainTo(mChannelResultsToProcess, 1000); int count = mChannelResultsQueue.drainTo(mChannelResultsToProcess, mMaxResultsToProcess);
while(!mChannelResultsToProcess.isEmpty()) if(count > 0)
{ {
for(float[] channelResults: mChannelResultsToProcess) process(mChannelResultsToProcess, listener);
{
process(channelResults, listener);
}
mChannelResultsToProcess.clear();
mChannelResultsQueue.drainTo(mChannelResultsToProcess, 1000);
} }
} }
catch(Throwable throwable) catch(Throwable throwable)
{ {
mLog.error("Error while processing polyphase channel samples", throwable); mLog.error("Error while processing polyphase channel samples", throwable);
mChannelResultsToProcess.clear();
} }
mChannelResultsToProcess.clear();
} }
/** /**
* Sub-class implementation to process one polyphase channelizer result array. * Sub-class implementation to process one polyphase channelizer result array.
* @param channelResult to process * @param channelResults to process
* @param listener to receive the channel complex sample * @param listener to receive the channel complex sample
*/ */
public abstract void process(float[] channelResult, ComplexSampleListener listener); public abstract void process(List<float[]> channelResults, ComplexSampleListener listener);
/** /**
@@ -133,16 +129,8 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
*/ */
protected float getFrequencyCorrectedInphase(float inphase, float quadrature) protected float getFrequencyCorrectedInphase(float inphase, float quadrature)
{ {
//Only apply frequency correction if the oscillator is set to a non-zero frequency offset return Complex.multiplyInphase(inphase, quadrature, mFrequencyCorrectionMixer.inphase(),
if(mFrequencyCorrectionMixer.enabled()) mFrequencyCorrectionMixer.quadrature());
{
return Complex.multiplyInphase(inphase, quadrature, mFrequencyCorrectionMixer.inphase(),
mFrequencyCorrectionMixer.quadrature());
}
else
{
return inphase;
}
} }
/** /**
@@ -155,16 +143,8 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
*/ */
protected float getFrequencyCorrectedQuadrature(float inphase, float quadrature) protected float getFrequencyCorrectedQuadrature(float inphase, float quadrature)
{ {
//Only apply frequency correction if the oscillator is set to a non-zero frequency offset return Complex.multiplyQuadrature(inphase, quadrature, mFrequencyCorrectionMixer.inphase(),
if(mFrequencyCorrectionMixer.enabled()) mFrequencyCorrectionMixer.quadrature());
{
return Complex.multiplyQuadrature(inphase, quadrature, mFrequencyCorrectionMixer.inphase(),
mFrequencyCorrectionMixer.quadrature());
}
else
{
return quadrature;
}
} }
/** /**
@@ -177,6 +157,16 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
public void setFrequencyCorrection(long frequencyCorrection) public void setFrequencyCorrection(long frequencyCorrection)
{ {
mFrequencyCorrectionMixer.setFrequency(frequencyCorrection); mFrequencyCorrectionMixer.setFrequency(frequencyCorrection);
mFrequencyCorrectionEnabled = (frequencyCorrection != 0);
}
/**
* Indicates if this channel has a frequency correction offset
*/
public boolean hasFrequencyCorrection()
{
return mFrequencyCorrectionEnabled;
} }
} }

View File

@@ -18,12 +18,17 @@
******************************************************************************/ ******************************************************************************/
package dsp.filter.channelizer.output; package dsp.filter.channelizer.output;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.complex.ComplexSampleListener; import sample.complex.ComplexSampleListener;
import java.util.Arrays;
import java.util.List; import java.util.List;
public class OneChannelOutputProcessor extends ChannelOutputProcessor public class OneChannelOutputProcessor extends ChannelOutputProcessor
{ {
private final static Logger mLog = LoggerFactory.getLogger(OneChannelOutputProcessor.class);
private int mChannelOffset; private int mChannelOffset;
/** /**
@@ -37,6 +42,7 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
{ {
super(1, sampleRate); super(1, sampleRate);
setPolyphaseChannelIndices(channelIndexes); setPolyphaseChannelIndices(channelIndexes);
mLog.debug("Sample Rate:" + sampleRate + " PolyPhase Channel Indexes:" + channelIndexes);
} }
/** /**
@@ -61,21 +67,31 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
* Extract the channel from the channel results array, apply frequency translation, and deliver the * Extract the channel from the channel results array, apply frequency translation, and deliver the
* extracted frequency-corrected channel I/Q sample set to the complex sample listener. * extracted frequency-corrected channel I/Q sample set to the complex sample listener.
* *
* @param channels to process containing an array of channel I/Q sample pairs (I0,Q0,I1,Q1...In,Qn) * @param channelResults to process containing a list of channel array of I/Q sample pairs (I0,Q0,I1,Q1...In,Qn)
* @param listener to receive the extracted, frequency-translated channel results * @param listener to receive the extracted, frequency-translated channel results
*/ */
@Override @Override
public void process(float[] channels, ComplexSampleListener listener) public void process(List<float[]> channelResults, ComplexSampleListener listener)
{ {
if(channels.length < mChannelOffset + 1) for(float[] channels: channelResults)
{ {
throw new IllegalArgumentException("Polyphase channelizer output channels array is not large enough to " + if(channels.length < mChannelOffset + 1)
"cover this single channel output processor with channel offset [" + mChannelOffset + "]"); {
throw new IllegalArgumentException("Polyphase channelizer output channels array is not large enough to " +
"cover this single channel output processor with channel offset [" + mChannelOffset + "]");
}
float i = channels[mChannelOffset];
float q = channels[mChannelOffset + 1];
if(hasFrequencyCorrection())
{
listener.receive(getFrequencyCorrectedInphase(i, q), getFrequencyCorrectedQuadrature(i, q));
}
else
{
listener.receive(i, q);
}
} }
float i = channels[mChannelOffset];
float q = channels[mChannelOffset + 1];
listener.receive(getFrequencyCorrectedInphase(i, q), getFrequencyCorrectedQuadrature(i, q));
} }
} }

View File

@@ -18,12 +18,16 @@
******************************************************************************/ ******************************************************************************/
package dsp.filter.channelizer.output; package dsp.filter.channelizer.output;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.complex.ComplexSampleListener; import sample.complex.ComplexSampleListener;
import java.util.List; import java.util.List;
public class TwoChannelOutputProcessor extends ChannelOutputProcessor public class TwoChannelOutputProcessor extends ChannelOutputProcessor
{ {
private final static Logger mLog = LoggerFactory.getLogger(TwoChannelOutputProcessor.class);
private int mChannelOffset1; private int mChannelOffset1;
private int mChannelOffset2; private int mChannelOffset2;
@@ -66,12 +70,13 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
* Extract the channel from the channel results array, apply frequency translation, and deliver the * Extract the channel from the channel results array, apply frequency translation, and deliver the
* extracted frequency-corrected channel I/Q sample set to the complex sample listener. * extracted frequency-corrected channel I/Q sample set to the complex sample listener.
* *
* @param channels to process containing an array of channel I/Q sample pairs (I0,Q0,I1,Q1...In,Qn) * @param channelResults to process containing an array of channel I/Q sample pairs (I0,Q0,I1,Q1...In,Qn)
* @param listener to receive the extracted, frequency-translated channel results * @param listener to receive the extracted, frequency-translated channel results
*/ */
@Override @Override
public void process(float[] channels, ComplexSampleListener listener) public void process(List<float[]> channelResults, ComplexSampleListener listener)
{ {
mLog.debug("I got channel results - doing nothing !!");
// if(channels.length < mChannelOffset + 1) // if(channels.length < mChannelOffset + 1)
// { // {
// throw new IllegalArgumentException("Polyphase channelizer output channels array is not large enough to " + // throw new IllegalArgumentException("Polyphase channelizer output channels array is not large enough to " +

View File

@@ -74,6 +74,14 @@ public class OverflowableTransferQueue<E>
} }
} }
/**
* Removes and returns a single element from the head of the queue or null if the queue is empty
*/
public E poll()
{
return mQueue.poll();
}
/** /**
* Retrieves elements from the queue into the collection up to the maximum number of elements specified * Retrieves elements from the queue into the collection up to the maximum number of elements specified
*/ */

View File

@@ -168,6 +168,14 @@ public abstract class Tuner implements ITunerChannelProvider
mSampleBroadcaster.removeListener(listener); mSampleBroadcaster.removeListener(listener);
} }
/**
* Number of registered complex sample buffer listeners
*/
public int getListenerCount()
{
return mSampleBroadcaster.getListenerCount();
}
/** /**
* Provides access to the internal sample broadcaster for sub-class use (ie protected). * Provides access to the internal sample broadcaster for sub-class use (ie protected).
*/ */

View File

@@ -19,6 +19,8 @@
package source.tuner.test; package source.tuner.test;
import dsp.mixer.LowPhaseNoiseOscillator; import dsp.mixer.LowPhaseNoiseOscillator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener; import sample.Listener;
import sample.complex.ComplexBuffer; import sample.complex.ComplexBuffer;
import util.ThreadPool; import util.ThreadPool;
@@ -28,12 +30,40 @@ import java.util.concurrent.TimeUnit;
public class SampleGenerator public class SampleGenerator
{ {
private final static Logger mLog = LoggerFactory.getLogger(SampleGenerator.class);
private LowPhaseNoiseOscillator mOscillator; private LowPhaseNoiseOscillator mOscillator;
private int mSweepUpdateInterval;
private Listener<ComplexBuffer> mListener; private Listener<ComplexBuffer> mListener;
private long mInterval; private long mInterval;
private int mSamplesPerInterval; private int mSamplesPerInterval;
private ScheduledFuture<?> mScheduledFuture; 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
* @param sweepUpdateRate to turn on sweeping of the tone frequency where the frequency is incremented by the
* rate specified at each interval, resetting to 1 Hz after reaching/exceeding the highest frequency.
*/
public SampleGenerator(int sampleRate, long frequency, long interval, int sweepUpdateRate)
{
if(Math.abs(sweepUpdateRate) >= sampleRate)
{
throw new IllegalArgumentException("Sweep update rate cannot be greater than sample rate");
}
mOscillator = new LowPhaseNoiseOscillator(sampleRate, frequency);
mInterval = interval;
mSweepUpdateInterval = sweepUpdateRate;
updateSamplesPerInterval();
}
/** /**
* Generates complex sample buffers at the specified sample rate with a unity gain tone at the specified * 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 * frequency. This generator runs via a scheduled thread pool and generates samples at the specified time
@@ -45,10 +75,7 @@ public class SampleGenerator
*/ */
public SampleGenerator(int sampleRate, long frequency, long interval) public SampleGenerator(int sampleRate, long frequency, long interval)
{ {
mOscillator = new LowPhaseNoiseOscillator(sampleRate, frequency); this(sampleRate, frequency, interval, 0);
mInterval = interval;
updateSamplesPerInterval();
} }
/** /**
@@ -56,7 +83,9 @@ public class SampleGenerator
*/ */
private void updateSamplesPerInterval() private void updateSamplesPerInterval()
{ {
mSamplesPerInterval = (int)((double)mOscillator.getSampleRate() * ((double)mInterval / 1000.0) * 2.0); mSamplesPerInterval = (int)((double)mOscillator.getSampleRate() * ((double)mInterval / 1000.0));
mLog.debug("Sample Generator: " + mSamplesPerInterval + " samples every " + mInterval + " ms.");
} }
/** /**
@@ -141,6 +170,24 @@ public class SampleGenerator
{ {
float[] samples = mOscillator.generate(mSamplesPerInterval); float[] samples = mOscillator.generate(mSamplesPerInterval);
mListener.receive(new ComplexBuffer(samples)); mListener.receive(new ComplexBuffer(samples));
if(mSweepUpdateInterval != 0)
{
long updatedFrequency = mOscillator.getFrequency() + mSweepUpdateInterval;
if(updatedFrequency > mOscillator.getSampleRate() / 2)
{
mOscillator.setFrequency(mOscillator.getSampleRate() / -2);
}
else if(updatedFrequency < mOscillator.getSampleRate() / -2)
{
mOscillator.setFrequency(mOscillator.getSampleRate() / 2);
}
else
{
mOscillator.setFrequency(updatedFrequency);
}
}
} }
} }
} }

View File

@@ -49,9 +49,6 @@ public class TestTuner extends Tuner
{ {
super("Test Tuner", new TestTunerController()); 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); mPolyphaseChannelManager = new PolyphaseChannelManager(this);
} }

View File

@@ -33,7 +33,7 @@ public class TestTunerController extends TunerController
public static final long MINIMUM_FREQUENCY = 1000000l; //1 MHz public static final long MINIMUM_FREQUENCY = 1000000l; //1 MHz
public static final long MAXIMUM_FREQUENCY = 10000000000l; //10 GHz public static final long MAXIMUM_FREQUENCY = 10000000000l; //10 GHz
public static final long DEFAULT_FREQUENCY = 100000000; //100 MHz public static final long DEFAULT_FREQUENCY = 100000000; //100 MHz
public static final int DEFAULT_SAMPLE_RATE = 1000000; //1 MHz public static final int DEFAULT_SAMPLE_RATE = 50000; //25 kHz
public static final int DC_NOISE_BANDWIDTH = 5000; // +/-5 kHz 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 public static final double USABLE_BANDWIDTH = 0.90; //90% usable bandwidth - 5% unusable on either end
@@ -46,7 +46,9 @@ public class TestTunerController extends TunerController
{ {
super(MINIMUM_FREQUENCY, MAXIMUM_FREQUENCY, DC_NOISE_BANDWIDTH, USABLE_BANDWIDTH); super(MINIMUM_FREQUENCY, MAXIMUM_FREQUENCY, DC_NOISE_BANDWIDTH, USABLE_BANDWIDTH);
mSampleGenerator = new SampleGenerator(DEFAULT_SAMPLE_RATE, DEFAULT_FREQUENCY, 100); int sweepRate = 0; //Hz per interval
mSampleGenerator = new SampleGenerator(DEFAULT_SAMPLE_RATE, DEFAULT_FREQUENCY, 100, sweepRate);
try try
{ {