Polyphase Channelizer Viewer is fully functional again, now using the

TestTuner and TestTunerController implementations.  This implementation
only has support for channels that are completely centered within a
channelizer output.  Two output slice stitching to form a composite
channel will be implemented next.

Initial profiling indicates that the FFT function within the polyphase
channelizer is now the dominant processing workload - 28% cpu loading
when channelizing a 10 MHz sample rate input into 800 channels running
at 25.0 kHz each.  This is ideal for offloading to an OpenCL kernel and
running on a GPU.
This commit is contained in:
Denny
2018-01-19 12:44:31 -05:00
parent 32ab0598e8
commit b6f282a85a
9 changed files with 208 additions and 102 deletions

View File

@@ -23,6 +23,7 @@ import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.source.ISourceEventProcessor; import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.Tuner;
import io.github.dsheirer.source.tuner.channel.TunerChannel; import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource; import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel; import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;
@@ -44,6 +45,8 @@ import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener; import javax.swing.event.ChangeListener;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.EventQueue; import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.List;
public class ChannelizerViewer extends JFrame public class ChannelizerViewer extends JFrame
{ {
@@ -55,13 +58,14 @@ public class ChannelizerViewer extends JFrame
private SettingsManager mSettingsManager = new SettingsManager(new TunerConfigurationModel()); private SettingsManager mSettingsManager = new SettingsManager(new TunerConfigurationModel());
private JPanel mPrimaryPanel; private JPanel mPrimaryPanel;
private JPanel mControlPanel; private JPanel mControlPanel;
private JLabel mToneFrequencyLabel;
private PrimarySpectrumPanel mPrimarySpectrumPanel; private PrimarySpectrumPanel mPrimarySpectrumPanel;
private ChannelArrayPanel mChannelPanel; private ChannelArrayPanel mChannelPanel;
private int mChannelCount; private int mChannelCount;
private int mChannelsPerRow; private int mChannelsPerRow;
private long mToneFrequency; private long mBaseFrequency = 100000000; //100 MHz
private DFTSize mMainPanelDFTSize = DFTSize.FFT32768; private DFTSize mMainPanelDFTSize = DFTSize.FFT32768;
private DFTSize mChannelPanelDFTSize = DFTSize.FFT01024; private DFTSize mChannelPanelDFTSize = DFTSize.FFT04096;
private TestTuner mTestTuner; private TestTuner mTestTuner;
/** /**
@@ -76,16 +80,12 @@ public class ChannelizerViewer extends JFrame
mChannelCount = (int)(mTestTuner.getTunerController().getUsableBandwidth() / CHANNEL_BANDWIDTH); mChannelCount = (int)(mTestTuner.getTunerController().getUsableBandwidth() / CHANNEL_BANDWIDTH);
mChannelsPerRow = channelsPerRow; mChannelsPerRow = channelsPerRow;
mToneFrequency = mTestTuner.getTunerController().getFrequency() + 19200;
mTestTuner.getTunerController().setToneFrequency(mToneFrequency);
init(); init();
} }
private void init() private void init()
{ {
setTitle("TunerChannelizer Viewer"); setTitle("Polyphase Channelizer Viewer");
setSize(1000, 800); setSize(1000, 800);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]")); setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
@@ -129,11 +129,11 @@ public class ChannelizerViewer extends JFrame
mControlPanel.setLayout(new MigLayout("insets 0 0 0 0", "", "")); mControlPanel.setLayout(new MigLayout("insets 0 0 0 0", "", ""));
mControlPanel.add(new JLabel("Tone:"), "align left"); mControlPanel.add(new JLabel("Tone:"), "align left");
int maximumFrequencyOffset = (int)(mTestTuner.getTunerController().getSampleRate() / 2); long minimumFrequency = -(long)mTestTuner.getTunerController().getSampleRate() / 2;
long minimumFrequency = mTestTuner.getTunerController().getFrequency() - maximumFrequencyOffset; long maximumFrequency = (long)mTestTuner.getTunerController().getSampleRate() / 2;
long maximumFrequency = mTestTuner.getTunerController().getFrequency() + maximumFrequencyOffset; long toneFrequency = 0;
SpinnerNumberModel model = new SpinnerNumberModel(mToneFrequency, minimumFrequency, maximumFrequency, SpinnerNumberModel model = new SpinnerNumberModel(toneFrequency, minimumFrequency, maximumFrequency,
100 ); 100 );
model.addChangeListener(new ChangeListener() model.addChangeListener(new ChangeListener()
@@ -141,8 +141,9 @@ public class ChannelizerViewer extends JFrame
@Override @Override
public void stateChanged(ChangeEvent e) public void stateChanged(ChangeEvent e)
{ {
int toneFrequency = model.getNumber().intValue(); long toneFrequency = model.getNumber().longValue();
mTestTuner.getTunerController().setToneFrequency(toneFrequency); mTestTuner.getTunerController().setToneFrequency(toneFrequency);
mToneFrequencyLabel.setText(String.valueOf(getToneFrequency()));
} }
}); });
@@ -151,15 +152,21 @@ 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: " + mTestTuner.getTunerController().getSampleRate() + " Hz"), "push,align right"); mControlPanel.add(new JLabel("Frequency:"), "push,align right");
mToneFrequencyLabel = new JLabel(String.valueOf(getToneFrequency()));
mControlPanel.add(mToneFrequencyLabel, "push,align left");
mControlPanel.add(new JLabel("Max: " + (mTestTuner.getTunerController().getSampleRate() / 2) + " Hz"), "push,align right");
mControlPanel.add(new JLabel("Channels: " + mChannelCount), "push,align right"); mControlPanel.add(new JLabel("Channels: " + mChannelCount), "push,align right");
} }
return mControlPanel; return mControlPanel;
} }
private long getToneFrequency()
{
return mTestTuner.getTunerController().getFrequency() + mTestTuner.getTunerController().getToneFrequency();
}
private ChannelArrayPanel getChannelArrayPanel() private ChannelArrayPanel getChannelArrayPanel()
{ {
if(mChannelPanel == null) if(mChannelPanel == null)
@@ -192,7 +199,7 @@ public class ChannelizerViewer extends JFrame
int channelToLog = -1; int channelToLog = -1;
long baseFrequency = 100006250; //100.006250 MHz long baseFrequency = mBaseFrequency + (CHANNEL_BANDWIDTH / 2);
for(int x = 0; x < mChannelCount - 1; x++) for(int x = 0; x < mChannelCount - 1; x++)
{ {
@@ -215,6 +222,33 @@ public class ChannelizerViewer extends JFrame
} }
} }
/**
* Returns a list of tuner channels that will fit within the tuner's bandwidth, minus a half channel each at the
* lower and upper ends of the spectrum.
*
* @param tuner to create channels for
* @return list of contiguous channels filling the tuner bandwidth
*/
public static List<TunerChannel> getTunerChannels(Tuner tuner)
{
List<TunerChannel> tunerChannels = new ArrayList<>();
long baseFrequency = tuner.getTunerController().getFrequency();
baseFrequency -= tuner.getTunerController().getSampleRate() / 2;
baseFrequency += (CHANNEL_BANDWIDTH / 2);
int channelCount = (int)(tuner.getTunerController().getSampleRate() / CHANNEL_BANDWIDTH) - 1;
for(int x = 0; x < channelCount; x++)
{
long frequency = baseFrequency + (x * CHANNEL_BANDWIDTH);
TunerChannel tunerChannel = new TunerChannel(frequency, CHANNEL_BANDWIDTH);
tunerChannels.add(tunerChannel);
}
return tunerChannels;
}
public class PrimarySpectrumPanel extends JPanel implements Listener<ComplexBuffer>, ISourceEventProcessor public class PrimarySpectrumPanel extends JPanel implements Listener<ComplexBuffer>, ISourceEventProcessor
{ {
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX); private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
@@ -225,6 +259,7 @@ public class ChannelizerViewer extends JFrame
{ {
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]")); setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
mSpectrumPanel = new SpectrumPanel(settingsManager); mSpectrumPanel = new SpectrumPanel(settingsManager);
mSpectrumPanel.setSampleSize(26);
add(mSpectrumPanel); add(mSpectrumPanel);
mDFTProcessor.addConverter(mComplexDecibelConverter); mDFTProcessor.addConverter(mComplexDecibelConverter);
@@ -256,21 +291,20 @@ 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, double sampleRate, long frequency, int bandwidth, boolean enableLogging) public ChannelPanel(SettingsManager settingsManager, double 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", "[center,grow,fill]", "[grow,fill][]"));
mSpectrumPanel = new SpectrumPanel(settingsManager); mSpectrumPanel = new SpectrumPanel(settingsManager);
add(mSpectrumPanel); mSpectrumPanel.setSampleSize(28);
add(mSpectrumPanel, "wrap");
add(new JLabel("Center:" + frequency));
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 = mTestTuner.getChannelSourceManager().getSource(tunerChannel); mSource = mTestTuner.getChannelSourceManager().getSource(tunerChannel);
@@ -326,18 +360,69 @@ public class ChannelizerViewer extends JFrame
public static void main(String[] args) public static void main(String[] args)
{ {
int channelsPerRow = 4; boolean useGUI = true;
final ChannelizerViewer frame = new ChannelizerViewer(channelsPerRow); if(useGUI)
EventQueue.invokeLater(new Runnable()
{ {
@Override int channelsPerRow = 4;
public void run()
final ChannelizerViewer frame = new ChannelizerViewer(channelsPerRow);
EventQueue.invokeLater(new Runnable()
{ {
frame.setVisible(true); @Override
public void run()
{
frame.setVisible(true);
}
});
}
else
{
TestTuner tuner = new TestTuner();
List<TunerChannel> tunerChannels = getTunerChannels(tuner);
List<TunerChannelSource> sources = new ArrayList<>();
int maxSourceCount = 30;
int sourceCount = 0;
for(TunerChannel tunerChannel: tunerChannels)
{
if(sourceCount < maxSourceCount)
{
TunerChannelSource source = tuner.getChannelSourceManager().getSource(tunerChannel);
if(source != null)
{
sources.add(source);
sourceCount++;
}
else
{
mLog.debug("Couldn't get source for: " + tunerChannel);
}
}
} }
});
mLog.debug("Starting [" + sources.size() + "] tuner channel sources");
for(TunerChannelSource tunerChannelSource: sources)
{
tunerChannelSource.setListener(new Listener<ComplexBuffer>()
{
@Override
public void receive(ComplexBuffer complexBuffer)
{
//Do nothing
}
});
tunerChannelSource.start();
}
while(true);
}
} }
} }

View File

@@ -197,11 +197,16 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
int half = getChannelCount() / 2; int half = getChannelCount() / 2;
int outputOffset = getChannelCount() - half - 1;
int outputIndex;
int sampleIndex;
int filterIndex;
for(int channel = 0; channel < half; channel++) for(int channel = 0; channel < half; channel++)
{ {
int outputIndex = 2 * (getChannelCount() - channel - 1); outputIndex = 2 * (outputOffset - channel);
int sampleIndex = 2 * channel; sampleIndex = 2 * channel;
int filterIndex = channel + half; filterIndex = channel + half;
processed[outputIndex] = filter(sampleIndex, mCoefficients[filterIndex], sampleIndexMap); processed[outputIndex] = filter(sampleIndex, mCoefficients[filterIndex], sampleIndexMap);
processed[outputIndex + 1] = filter(sampleIndex + 1, mCoefficients[filterIndex], sampleIndexMap); processed[outputIndex + 1] = filter(sampleIndex + 1, mCoefficients[filterIndex], sampleIndexMap);
@@ -209,12 +214,13 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
//The second half of the sample buffer was loaded using the previous column pointer - adjust for that now //The second half of the sample buffer was loaded using the previous column pointer - adjust for that now
sampleIndexMap = mSampleIndexMap[(mColumnPointer == (mSamples.length - 1) ? 0 : mColumnPointer + 1)]; sampleIndexMap = mSampleIndexMap[(mColumnPointer == (mSamples.length - 1) ? 0 : mColumnPointer + 1)];
outputOffset = getChannelCount() + half - 1;
for(int channel = half; channel < getChannelCount(); channel++) for(int channel = half; channel < getChannelCount(); channel++)
{ {
int outputIndex = 2 * (getChannelCount() - channel - 1); outputIndex = 2 * (outputOffset - channel);
int sampleIndex = 2 * channel; sampleIndex = 2 * channel;
int filterIndex = channel - half; filterIndex = channel - half;
processed[outputIndex] = filter(sampleIndex, mCoefficients[filterIndex], sampleIndexMap); processed[outputIndex] = filter(sampleIndex, mCoefficients[filterIndex], sampleIndexMap);
processed[outputIndex + 1] = filter(sampleIndex + 1, mCoefficients[filterIndex], sampleIndexMap); processed[outputIndex + 1] = filter(sampleIndex + 1, mCoefficients[filterIndex], sampleIndexMap);

View File

@@ -132,7 +132,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
try try
{ {
List<Integer> polyphaseIndexes = mChannelCalculator.getChannelIndexes(tunerChannel); List<Integer> polyphaseIndexes = mChannelCalculator.getChannelIndexes(tunerChannel);
mLog.debug("Getting polyphase channel for: " + tunerChannel + " indexes:" + polyphaseIndexes);
IPolyphaseChannelOutputProcessor outputProcessor = getOutputProcessor(polyphaseIndexes); IPolyphaseChannelOutputProcessor outputProcessor = getOutputProcessor(polyphaseIndexes);
@@ -240,7 +239,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
{ {
case NOTIFICATION_FREQUENCY_CHANGE: case NOTIFICATION_FREQUENCY_CHANGE:
//Update channel calculator immediately so that channels can be allocated //Update channel calculator immediately so that channels can be allocated
mLog.debug("Updating channel calculator to frequency: " + sourceEvent.getValue());
mChannelCalculator.setCenterFrequency(sourceEvent.getValue().longValue()); mChannelCalculator.setCenterFrequency(sourceEvent.getValue().longValue());
//Defer channelizer configuration changes to be handled on the buffer processor thread //Defer channelizer configuration changes to be handled on the buffer processor thread
@@ -248,7 +246,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
break; break;
case NOTIFICATION_SAMPLE_RATE_CHANGE: case NOTIFICATION_SAMPLE_RATE_CHANGE:
//Update channel calculator immediately so that channels can be allocated //Update channel calculator immediately so that channels can be allocated
mLog.debug("Updating channel calculator to sample rate: " + sourceEvent.getValue());
mChannelCalculator.setSampleRate(sourceEvent.getValue().doubleValue()); mChannelCalculator.setSampleRate(sourceEvent.getValue().doubleValue());
//Defer channelizer configuration changes to be handled on the buffer processor thread //Defer channelizer configuration changes to be handled on the buffer processor thread
@@ -355,13 +352,11 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
//If the indexes size is the same then update the current processor, otherwise create a new one //If the indexes size is the same then update the current processor, otherwise create a new one
if(channelSource.getPolyphaseChannelOutputProcessor().getInputChannelCount() == indexes.size()) if(channelSource.getPolyphaseChannelOutputProcessor().getInputChannelCount() == indexes.size())
{ {
mLog.debug("Updating indexes to " + indexes + " for: " + channelSource.getTunerChannel());
channelSource.getPolyphaseChannelOutputProcessor().setPolyphaseChannelIndices(indexes); channelSource.getPolyphaseChannelOutputProcessor().setPolyphaseChannelIndices(indexes);
channelSource.setFrequency(centerFrequency); channelSource.setFrequency(centerFrequency);
} }
else else
{ {
mLog.debug("Creating new output processor for indexes " + indexes);
channelSource.setPolyphaseChannelOutputProcessor(getOutputProcessor(indexes), centerFrequency); channelSource.setPolyphaseChannelOutputProcessor(getOutputProcessor(indexes), centerFrequency);
} }
} }
@@ -487,17 +482,13 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
switch(queuedSourceEvent.getEvent()) switch(queuedSourceEvent.getEvent())
{ {
case NOTIFICATION_FREQUENCY_CHANGE: case NOTIFICATION_FREQUENCY_CHANGE:
mLog.debug("******Frequency change event");
updateOutputProcessors(queuedSourceEvent); updateOutputProcessors(queuedSourceEvent);
break; break;
case NOTIFICATION_SAMPLE_RATE_CHANGE: case NOTIFICATION_SAMPLE_RATE_CHANGE:
mLog.debug("******Sample Rate change event");
updateChannelizerSampleRate(queuedSourceEvent.getValue().intValue()); updateChannelizerSampleRate(queuedSourceEvent.getValue().intValue());
break; break;
} }
mLog.debug("****** Source event processed ... continuing");
queuedSourceEvent = mQueuedSourceEvents.poll(); queuedSourceEvent = mQueuedSourceEvents.poll();
} }

View File

@@ -72,10 +72,10 @@ public class PolyphaseChannelResultsBuffer
{ {
throw new IllegalArgumentException("Buffer is full - cannot add new channel results"); throw new IllegalArgumentException("Buffer is full - cannot add new channel results");
} }
if(mPointer == 0) if(mPointer == 0)
{ {
mChannelResults = new float[mMaxSize][channelResults.length]; mChannelResults = new float[mMaxSize][channelResults.length];
mChannelResults[mPointer++] = channelResults;
} }
else else
{ {
@@ -87,9 +87,9 @@ public class PolyphaseChannelResultsBuffer
"a different length than the channel results that are already contained in this buffer [" + "a different length than the channel results that are already contained in this buffer [" +
currentLength + "]"); currentLength + "]");
} }
mChannelResults[mPointer++] = channelResults;
} }
mChannelResults[mPointer++] = channelResults;
} }
/** /**
@@ -97,10 +97,12 @@ public class PolyphaseChannelResultsBuffer
*/ */
public float[][] getChannelResults() public float[][] getChannelResults()
{ {
//If this buffer is full, return all of the channel results
if(mPointer == mMaxSize) if(mPointer == mMaxSize)
{ {
return mChannelResults; return mChannelResults;
} }
//Otherwise, truncate the results to return
else else
{ {
return Arrays.copyOf(mChannelResults, mPointer); return Arrays.copyOf(mChannelResults, mPointer);
@@ -120,6 +122,7 @@ public class PolyphaseChannelResultsBuffer
{ {
throw new IllegalArgumentException("Buffer is empty - cannot extract channel [" + iChannelIndex + "]"); throw new IllegalArgumentException("Buffer is empty - cannot extract channel [" + iChannelIndex + "]");
} }
if(!isValidChannelIndex(iChannelIndex)) if(!isValidChannelIndex(iChannelIndex))
{ {
throw new IllegalArgumentException("Channel [" + iChannelIndex + "] is not valid for the contained channel " + throw new IllegalArgumentException("Channel [" + iChannelIndex + "] is not valid for the contained channel " +
@@ -131,8 +134,10 @@ public class PolyphaseChannelResultsBuffer
int qChannelIndex = iChannelIndex + 1; int qChannelIndex = iChannelIndex + 1;
for(float[] channelResults: mChannelResults) for(int x = 0; x < mPointer; x++)
{ {
float[] channelResults = mChannelResults[x];
samples[pointer++] = channelResults[iChannelIndex]; samples[pointer++] = channelResults[iChannelIndex];
samples[pointer++] = channelResults[qChannelIndex]; samples[pointer++] = channelResults[qChannelIndex];
} }

View File

@@ -60,15 +60,6 @@ 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

@@ -42,7 +42,6 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
{ {
super(1, sampleRate); super(1, sampleRate);
setPolyphaseChannelIndices(channelIndexes); setPolyphaseChannelIndices(channelIndexes);
mLog.debug("Sample Rate:" + sampleRate + " PolyPhase Channel Indexes:" + channelIndexes);
} }
/** /**

View File

@@ -161,12 +161,12 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
return mFrequencyController.getMaximumFrequency(); return mFrequencyController.getMaximumFrequency();
} }
private long getMinTunedFrequency() throws SourceException public long getMinTunedFrequency() throws SourceException
{ {
return mFrequencyController.getFrequency() - (getUsableBandwidth() / 2); return mFrequencyController.getFrequency() - (getUsableBandwidth() / 2);
} }
private long getMaxTunedFrequency() throws SourceException public long getMaxTunedFrequency() throws SourceException
{ {
return mFrequencyController.getFrequency() + (getUsableBandwidth() / 2); return mFrequencyController.getFrequency() + (getUsableBandwidth() / 2);
} }

View File

@@ -36,6 +36,7 @@ public class SampleGenerator
private int mSweepUpdateInterval; private int mSweepUpdateInterval;
private long mInterval; private long mInterval;
private int mSamplesPerInterval; private int mSamplesPerInterval;
private boolean mReuseBuffers;
private ScheduledFuture<?> mScheduledFuture; private ScheduledFuture<?> mScheduledFuture;
/** /**
@@ -49,17 +50,17 @@ public class SampleGenerator
* @param sweepUpdateRate to turn on sweeping of the tone frequency where the frequency is incremented by the * @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. * 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) public SampleGenerator(int sampleRate, long frequency, long interval, int sweepUpdateRate, boolean reuseBuffers)
{ {
if(Math.abs(sweepUpdateRate) >= sampleRate) if(Math.abs(sweepUpdateRate) >= sampleRate)
{ {
throw new IllegalArgumentException("Sweep update rate cannot be greater than sample rate"); throw new IllegalArgumentException("Sweep update rate cannot be greater than sample rate");
} }
mLog.debug("Sample Generator - Sample Rate:" + sampleRate + " Frequency:" + frequency);
mOscillator = new LowPhaseNoiseOscillator(sampleRate, frequency); mOscillator = new LowPhaseNoiseOscillator(sampleRate, frequency);
mInterval = interval; mInterval = interval;
mSweepUpdateInterval = sweepUpdateRate; mSweepUpdateInterval = sweepUpdateRate;
mReuseBuffers = reuseBuffers;
updateSamplesPerInterval(); updateSamplesPerInterval();
} }
@@ -73,9 +74,9 @@ public class SampleGenerator
* @param frequency of the tone produced by the generator * @param frequency of the tone produced by the generator
* @param interval in milliseconds for generating samples * @param interval in milliseconds for generating samples
*/ */
public SampleGenerator(int sampleRate, long frequency, long interval) public SampleGenerator(int sampleRate, long frequency, long interval, boolean reuseBuffers)
{ {
this(sampleRate, frequency, interval, 0); this(sampleRate, frequency, interval, 0, reuseBuffers);
} }
/** /**
@@ -84,8 +85,6 @@ public class SampleGenerator
private void updateSamplesPerInterval() private void updateSamplesPerInterval()
{ {
mSamplesPerInterval = (int)((double)mOscillator.getSampleRate() * ((double)mInterval / 1000.0)); mSamplesPerInterval = (int)((double)mOscillator.getSampleRate() * ((double)mInterval / 1000.0));
mLog.debug("Sample Generator - " + mSamplesPerInterval + " samples every " + mInterval + "ms");
} }
/** /**
@@ -175,35 +174,65 @@ public class SampleGenerator
updateSamplesPerInterval(); updateSamplesPerInterval();
} }
public double getSampleRate()
{
return mOscillator.getSampleRate();
}
/** /**
* Generates a complex sample buffer and distributes the buffer to a registered listener * Generates a complex sample buffer and distributes the buffer to a registered listener
*/ */
public class Generator implements Runnable public class Generator implements Runnable
{ {
private int mTriggerInterval = 0;
private long mOscillatorFrequency = 1;
private ComplexBuffer mReusableBuffer;
@Override @Override
public void run() public void run()
{ {
if(mComplexBufferBroadcaster.hasListeners()) if(mComplexBufferBroadcaster.hasListeners())
{ {
float[] samples = mOscillator.generate(mSamplesPerInterval); if(mReuseBuffers)
{
if(mReusableBuffer == null ||
mReusableBuffer.getSamples().length != mSamplesPerInterval ||
mOscillatorFrequency != mOscillator.getFrequency())
{
mOscillatorFrequency = mOscillator.getFrequency();
mReusableBuffer = new TimestampedComplexBuffer(mOscillator.generate(mSamplesPerInterval));
}
mComplexBufferBroadcaster.receive(new TimestampedComplexBuffer(samples)); mComplexBufferBroadcaster.receive(mReusableBuffer);
}
else
{
float[] samples = mOscillator.generate(mSamplesPerInterval);
mComplexBufferBroadcaster.receive(new TimestampedComplexBuffer(samples));
}
if(mSweepUpdateInterval != 0) if(mSweepUpdateInterval != 0)
{ {
long updatedFrequency = mOscillator.getFrequency() + mSweepUpdateInterval; mTriggerInterval++;
if(updatedFrequency > mOscillator.getSampleRate() / 2) if(mTriggerInterval >= 10)
{ {
mOscillator.setFrequency(mOscillator.getSampleRate() / -2); mTriggerInterval = 0;
}
else if(updatedFrequency < mOscillator.getSampleRate() / -2) long updatedFrequency = mOscillator.getFrequency() + mSweepUpdateInterval;
{
mOscillator.setFrequency(mOscillator.getSampleRate() / 2); if(updatedFrequency > mOscillator.getSampleRate() / 2)
} {
else mOscillator.setFrequency(mOscillator.getSampleRate() / -2);
{ }
mOscillator.setFrequency(updatedFrequency); else if(updatedFrequency < mOscillator.getSampleRate() / -2)
{
mOscillator.setFrequency(mOscillator.getSampleRate() / 2);
}
else
{
mOscillator.setFrequency(updatedFrequency);
}
} }
} }
} }

View File

@@ -30,17 +30,18 @@ public class TestTunerController extends TunerController
{ {
private final static Logger mLog = LoggerFactory.getLogger(TestTunerController.class); private final static Logger mLog = LoggerFactory.getLogger(TestTunerController.class);
public static final long MINIMUM_FREQUENCY = 1000000l; //1 MHz public static final long MINIMUM_FREQUENCY = 1l;
public static final long MAXIMUM_FREQUENCY = 10000000000l; //10 GHz public static final long MAXIMUM_FREQUENCY = 1000000000l;
public static final long DEFAULT_CENTER_FREQUENCY = 100000000; //100 MHz public static final int SAMPLE_RATE = 225000;
public static final int DEFAULT_SAMPLE_RATE = 100000; //25 kHz public static final int DC_NOISE_BANDWIDTH = 0;
public static final int DC_NOISE_BANDWIDTH = 0; // +/-5 kHz public static final double USABLE_BANDWIDTH = 1.00;
public static final double USABLE_BANDWIDTH = 1.00; //100% usable bandwidth
public static final int SPECTRAL_FRAME_RATE = 20; public static final int SPECTRAL_FRAME_RATE = 20;
public static final long SAMPLE_GENERATION_INTERVAL = 1000 / SPECTRAL_FRAME_RATE; public static final long SAMPLE_GENERATION_INTERVAL = 1000 / SPECTRAL_FRAME_RATE;
private SampleGenerator mSampleGenerator; private SampleGenerator mSampleGenerator;
private long mReferenceFrequency = DEFAULT_CENTER_FREQUENCY; private boolean mReuseBuffers = true;
private long mFrequency = 100000000l;
/** /**
* Tuner controller testing implementation. * Tuner controller testing implementation.
@@ -50,15 +51,14 @@ 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);
int sweepRate = 0; //Hz per interval int sweepRate = 0; //Hz per interval
long initialToneFrequency = SAMPLE_RATE / 2 + 100;
mSampleGenerator = new SampleGenerator(DEFAULT_SAMPLE_RATE, 12500, SAMPLE_GENERATION_INTERVAL, sweepRate); mSampleGenerator = new SampleGenerator(SAMPLE_RATE, initialToneFrequency, SAMPLE_GENERATION_INTERVAL,
sweepRate, mReuseBuffers);
try try
{ {
mLog.debug("Setting frequency to:" + DEFAULT_CENTER_FREQUENCY); mFrequencyController.setFrequency(mFrequency);
mFrequencyController.setFrequency(DEFAULT_CENTER_FREQUENCY); mFrequencyController.setSampleRate(SAMPLE_RATE);
mLog.debug("Setting sample rate of freq controller to: " + DEFAULT_SAMPLE_RATE);
mFrequencyController.setSampleRate(DEFAULT_SAMPLE_RATE);
} }
catch(Exception e) catch(Exception e)
{ {
@@ -91,7 +91,7 @@ public class TestTunerController extends TunerController
@Override @Override
public long getTunedFrequency() throws SourceException public long getTunedFrequency() throws SourceException
{ {
return mReferenceFrequency; return mFrequency;
} }
/** /**
@@ -102,26 +102,26 @@ public class TestTunerController extends TunerController
@Override @Override
public void setTunedFrequency(long frequency) throws SourceException public void setTunedFrequency(long frequency) throws SourceException
{ {
mReferenceFrequency = frequency; mFrequency = frequency;
} }
/** /**
* Sets the tone frequency for the sample generator. The tone frequency is offset from the center tuned frequency. * Sets the tone output of the frequency generator
* @param toneFrequency * @param frequency in the range: 0 <> Sample Rate
*/ */
public void setToneFrequency(long toneFrequency) public void setToneFrequency(long frequency)
{ {
long frequency = toneFrequency - mReferenceFrequency;
mSampleGenerator.setFrequency(frequency); mSampleGenerator.setFrequency(frequency);
} }
/** /**
* Current tone frequency for this test controller. * Frequency of the tone being generated
* @return *
* @return tone frequency in range: 0 <> Sample Rate
*/ */
public long getToneFrequency() public long getToneFrequency()
{ {
return mReferenceFrequency + mSampleGenerator.getFrequency(); return mSampleGenerator.getFrequency();
} }
/** /**
@@ -130,7 +130,7 @@ public class TestTunerController extends TunerController
@Override @Override
public double getCurrentSampleRate() public double getCurrentSampleRate()
{ {
return mFrequencyController.getSampleRate(); return mSampleGenerator.getSampleRate();
} }
/** /**
@@ -138,6 +138,6 @@ public class TestTunerController extends TunerController
*/ */
public void setSampleRate(int sampleRate) throws SourceException public void setSampleRate(int sampleRate) throws SourceException
{ {
mFrequencyController.setSampleRate(sampleRate); mSampleGenerator.setSampleRate(sampleRate);
} }
} }