[WIP] working on polyphase channelizer and testing viewer

This commit is contained in:
Denny
2017-02-19 08:22:37 -05:00
parent e868428c88
commit 99ef002d07
5 changed files with 387 additions and 45 deletions

View File

@@ -0,0 +1,106 @@
/*******************************************************************************
* 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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Broadcaster;
import sample.Listener;
import sample.complex.ComplexBuffer;
import java.util.HashMap;
import java.util.Map;
public class ChannelDistributor
{
private final static Logger mLog = LoggerFactory.getLogger(ChannelDistributor.class);
private Map<Integer,ComplexSampleAssembler> mListenerMap = new HashMap<>();
private int mBufferSize;
private int mChannelCount;
public ChannelDistributor(int bufferSize, int channelCount)
{
mBufferSize = bufferSize;
mChannelCount = channelCount;
}
public void receive(float[] channels)
{
for(int channel = 0; channel < channels.length / 2; channel++)
{
if(mListenerMap.containsKey(channel))
{
ComplexSampleAssembler channelAssembler = mListenerMap.get(channel);
int index = channel * 2;
channelAssembler.receive(channels[index], channels[index + 1]);
}
}
}
public void addListener(int channel, Listener<ComplexBuffer> listener)
{
ComplexSampleAssembler channelAssembler = mListenerMap.get(channel);
if(channelAssembler == null)
{
channelAssembler = new ComplexSampleAssembler(mBufferSize);
mListenerMap.put(channel, channelAssembler);
}
channelAssembler.addListener(listener);
}
public class ComplexSampleAssembler
{
private Broadcaster<ComplexBuffer> mBroadcaster = new Broadcaster<>();
private float[] mBuffer;
private int mBufferPointer;
private int mBufferSize;
private boolean mEnableLogging;
public ComplexSampleAssembler(int bufferSize)
{
mBufferSize = bufferSize;
mBuffer = new float[mBufferSize];
}
public void receive(float inphase, float quadrature)
{
mBuffer[mBufferPointer++] = inphase;
mBuffer[mBufferPointer++] = inphase;
if(mBufferPointer >= mBufferSize)
{
float[] bufferCopy = new float[mBuffer.length];
System.arraycopy(mBuffer,0, bufferCopy, 0, mBuffer.length);
mBroadcaster.receive(new ComplexBuffer(bufferCopy));
mBufferPointer = 0;
}
}
public void addListener(Listener<ComplexBuffer> listener)
{
mBroadcaster.addListener(listener);
}
}
}

View File

@@ -19,6 +19,7 @@
package dsp.filter.channelizer;
import dsp.filter.FilterFactory;
import dsp.mixer.LowPhaseNoiseOscillator;
import dsp.mixer.Oscillator;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
@@ -35,6 +36,8 @@ import spectrum.SpectrumPanel;
import spectrum.converter.ComplexDecibelConverter;
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;
@@ -45,14 +48,14 @@ public class ChannelizerViewer extends JFrame
private final static Logger mLog = LoggerFactory.getLogger(ChannelizerViewer.class);
private static final int CHANNEL_BANDWIDTH = 12500;
private static final int CHANNEL_FFT_FRAME_RATE = 20;
private SettingsManager mSettingsManager = new SettingsManager(new TunerConfigurationModel());
private JPanel mPrimaryPanel;
private SpectrumPanel mSpectrumPanel;
private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter();
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
private ChannelPanel mChannelPanel;
private Oscillator mOscillator;
private JPanel mControlPanel;
private ChannelPanel mPrimarySpectrumPanel;
private ChannelArrayPanel mChannelPanel;
private LowPhaseNoiseOscillator mOscillator;
private int mChannelCount;
private int mSampleRate;
private int mToneFrequency;
@@ -62,7 +65,7 @@ public class ChannelizerViewer extends JFrame
{
mChannelCount = channelCount;
mSampleRate = mChannelCount * CHANNEL_BANDWIDTH;
mToneFrequency = 37500; //Set to channel 1 as default
mToneFrequency = (int)(CHANNEL_BANDWIDTH * 3); //Set to channel 1 as default
init();
}
@@ -76,13 +79,13 @@ public class ChannelizerViewer extends JFrame
setLocationRelativeTo(null);
add(getPrimaryPanel());
mOscillator = new Oscillator(mToneFrequency, mSampleRate);
mOscillator = new LowPhaseNoiseOscillator(mSampleRate, mToneFrequency);
}
public void start()
{
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
scheduledExecutorService.scheduleAtFixedRate(new SampleGenerationTask(), 0, 100, TimeUnit.MILLISECONDS);
scheduledExecutorService.scheduleAtFixedRate(new SampleGenerationTask(), 0, 50, TimeUnit.MILLISECONDS);
}
public void setToneFrequency(int frequency)
@@ -93,22 +96,14 @@ public class ChannelizerViewer extends JFrame
private void generateSamples()
{
int size = mDFTSize.getSize();
int size = mChannelCount * CHANNEL_BANDWIDTH / CHANNEL_FFT_FRAME_RATE * 2;
float[] samples = new float[size * 2];
for(int x = 0; x < size; x += 2)
{
samples[x] = mOscillator.getComplex().inphase();
samples[x + 1] = mOscillator.getComplex().quadrature();
mOscillator.rotate();
}
float[] samples = mOscillator.generate(size);
ComplexBuffer buffer = new ComplexBuffer(samples);
getChannelPanel().receive(buffer);
mDFTProcessor.receive(buffer);
getChannelArrayPanel().receive(buffer);
getSpectrumPanel().receive(buffer);
}
private JPanel getPrimaryPanel()
@@ -116,34 +111,60 @@ public class ChannelizerViewer extends JFrame
if(mPrimaryPanel == null)
{
mPrimaryPanel = new JPanel();
mPrimaryPanel.setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[][grow,fill]"));
mPrimaryPanel.setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[][][grow,fill]"));
mPrimaryPanel.add(getSpectrumPanel(), "wrap");
mPrimaryPanel.add(getChannelPanel());
mPrimaryPanel.add(getControlPanel(), "wrap");
mPrimaryPanel.add(getChannelArrayPanel());
}
return mPrimaryPanel;
}
private SpectrumPanel getSpectrumPanel()
private ChannelPanel getSpectrumPanel()
{
if(mSpectrumPanel == null)
if(mPrimarySpectrumPanel == null)
{
mSpectrumPanel = new SpectrumPanel(mSettingsManager);
mSpectrumPanel.setPreferredSize(new Dimension(1000, 200));
mComplexDecibelConverter.addListener(mSpectrumPanel);
mDFTProcessor.addConverter(mComplexDecibelConverter);
mDFTProcessor.frequencyChanged(new FrequencyChangeEvent(
FrequencyChangeEvent.Event.NOTIFICATION_SAMPLE_RATE_CHANGE, mSampleRate));
mPrimarySpectrumPanel = new ChannelPanel(mSettingsManager, mSampleRate);
mPrimarySpectrumPanel.setPreferredSize(new Dimension(1000, 200));
mPrimarySpectrumPanel.setDFTSize(DFTSize.FFT32768);
}
return mSpectrumPanel;
return mPrimarySpectrumPanel;
}
private ChannelPanel getChannelPanel()
private JPanel getControlPanel()
{
if(mControlPanel == null)
{
mControlPanel = new JPanel();
int maximumFrequency = mSampleRate / 2;
SpinnerNumberModel model = new SpinnerNumberModel(mToneFrequency, -maximumFrequency, maximumFrequency, CHANNEL_BANDWIDTH );
model.addChangeListener(new ChangeListener()
{
@Override
public void stateChanged(ChangeEvent e)
{
int frequency = model.getNumber().intValue();
mOscillator.setFrequency(frequency);
}
});
JSpinner spinner = new JSpinner(model);
mControlPanel.add(spinner);
}
return mControlPanel;
}
private ChannelArrayPanel getChannelArrayPanel()
{
if(mChannelPanel == null)
{
mChannelPanel = new ChannelPanel(getFilter());
mChannelPanel = new ChannelArrayPanel(getFilter());
}
return mChannelPanel;
@@ -170,13 +191,47 @@ public class ChannelizerViewer extends JFrame
return taps;
}
public class ChannelPanel extends JPanel implements Listener<ComplexBuffer>
public class ChannelArrayPanel extends JPanel implements Listener<ComplexBuffer>
{
private PolyphaseChannelizer mPolyphaseChannelizer;
private ChannelDistributor mChannelDistributor;
public ChannelPanel(float[] taps)
public ChannelArrayPanel(float[] taps)
{
int bufferSize = CHANNEL_BANDWIDTH / CHANNEL_FFT_FRAME_RATE;
if(bufferSize % 2 == 1)
{
bufferSize++;
}
mPolyphaseChannelizer = new PolyphaseChannelizer(taps, mChannelCount);
mChannelDistributor = new ChannelDistributor(bufferSize, mChannelCount);
mPolyphaseChannelizer.setChannelDistributor(mChannelDistributor);
init();
}
private void init()
{
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill][grow,fill][grow,fill][grow,fill]", ""));
for(int x = 0; x < mChannelCount; x++)
{
ChannelPanel channelPanel = new ChannelPanel(mSettingsManager, CHANNEL_BANDWIDTH * 2);
channelPanel.setPreferredSize(new Dimension(250,100));
channelPanel.setDFTSize(DFTSize.FFT00512);
if(x % 4 == 3)
{
add(channelPanel, "wrap");
}
else
{
add(channelPanel);
}
mChannelDistributor.addListener(x, channelPanel);
}
}
@Override
@@ -186,6 +241,36 @@ public class ChannelizerViewer extends JFrame
}
}
public class ChannelPanel extends JPanel implements Listener<ComplexBuffer>
{
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter();
private SpectrumPanel mSpectrumPanel;
public ChannelPanel(SettingsManager settingsManager, int sampleRate)
{
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
mSpectrumPanel = new SpectrumPanel(settingsManager);
add(mSpectrumPanel);
mDFTProcessor.addConverter(mComplexDecibelConverter);
mDFTProcessor.frequencyChanged(new FrequencyChangeEvent(
FrequencyChangeEvent.Event.NOTIFICATION_SAMPLE_RATE_CHANGE, sampleRate));
mComplexDecibelConverter.addListener(mSpectrumPanel);
}
public void setDFTSize(DFTSize dftSize)
{
mDFTProcessor.setDFTSize(dftSize);
}
@Override
public void receive(ComplexBuffer complexBuffer)
{
mDFTProcessor.receive(complexBuffer);
}
}
public class SampleGenerator
{
public SampleGenerator()
@@ -218,7 +303,9 @@ public class ChannelizerViewer extends JFrame
public static void main(String[] args)
{
final ChannelizerViewer frame = new ChannelizerViewer(16);
int channelCount = 24;
final ChannelizerViewer frame = new ChannelizerViewer(channelCount);
EventQueue.invokeLater(new Runnable()
{

View File

@@ -43,6 +43,7 @@ public class PolyphaseChannelizer implements Listener<ComplexBuffer>
private float[] mFilteredSamples;
private boolean mToggle = false;
private FloatFFT_1D mFFT;
private ChannelDistributor mChannelDistributor;
private DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0000");
@@ -125,15 +126,15 @@ public class PolyphaseChannelizer implements Listener<ComplexBuffer>
private void dispatch(float[] channels)
{
// StringBuilder sb = new StringBuilder();
//
// for(int x = 0; x < channels.length; x += 2)
// {
// sb.append((x/2)).append("[")
// .append(DECIMAL_FORMAT.format(magnitude(channels[x], channels[x + 1], mChannelCount))).append("] ");
// }
//
// mLog.debug("Out: " + sb.toString());
if(mChannelDistributor != null)
{
mChannelDistributor.receive(channels);
}
}
public void setChannelDistributor(ChannelDistributor channelDistributor)
{
mChannelDistributor = channelDistributor;
}
private double magnitude(float real, float imaginary, int fftSize)

View File

@@ -0,0 +1,126 @@
/*******************************************************************************
* 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.mixer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
public class LowPhaseNoiseOscillator
{
private final static Logger mLog = LoggerFactory.getLogger(LowPhaseNoiseOscillator.class);
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 float mInphase = 1.0f;
private float mQuadrature = 0.0f;
private float mPreviousInphase = 1.0f;
private float mPreviousQuadrature = 0.0f;
private float mCosineAngle;
private float mSineAngle;
private float mGain = 1.0f;
/**
* Ultra-low phase noise complex oscillator as described in Digital Signal Processing 3e, Lyons, p.786
*
* @param sampleRate for the oscillator
* @param frequency to generate
*/
public LowPhaseNoiseOscillator(int sampleRate, int frequency)
{
mSampleRate = sampleRate;
mFrequency = frequency;
update();
}
public void setSampleRate(int sampleRate)
{
mSampleRate = sampleRate;
update();
}
public void setFrequency(int frequency)
{
mFrequency = frequency;
update();
}
/**
* Updates the internal values after a frequency or sample rate change
*/
private void update()
{
double anglePerSample = TWO_PI * mFrequency / mSampleRate;
mCosineAngle = (float)Math.cos(anglePerSample);
mSineAngle = (float)Math.sin(anglePerSample);
}
private void rotate()
{
mInphase = ((mPreviousInphase * mCosineAngle) - (mPreviousQuadrature * mSineAngle)) * mGain;
mQuadrature = ((mPreviousInphase * mSineAngle) + (mPreviousQuadrature * mCosineAngle)) * mGain;
mPreviousInphase = mInphase;
mPreviousQuadrature = mQuadrature;
mGain = THREE_HALVES - ((mPreviousInphase * mPreviousInphase) + (mPreviousQuadrature * mPreviousQuadrature));
}
/**
* Generates an array of samples with length of 2 x sample count containing interleaved inphase and quadrature
* samples.
*
* @param sampleCount number of samples to generate
* @return array that is sampleCount * 2 in length
*/
public float[] generate(int sampleCount)
{
float[] samples = new float[sampleCount * 2];
for(int x = 0; x < sampleCount; x++)
{
int index = x * 2;
samples[index] = mInphase;
samples[index + 1] = mQuadrature;
rotate();
}
return samples;
}
public static void main(String[] args)
{
LowPhaseNoiseOscillator o = new LowPhaseNoiseOscillator(10, 1);
float[] samples = o.generate(20);
StringBuilder sb = new StringBuilder();
for(int x = 0; x < samples.length; x += 2)
{
sb.append(samples[x]).append(" ").append(samples[x + 1]).append("\n");
}
mLog.debug("Samples:\n" + sb.toString());
}
}

View File

@@ -21,6 +21,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.complex.Complex;
import sample.complex.ComplexBuffer;
public class Oscillator
{
@@ -110,4 +111,25 @@ public class Oscillator
{
return mCurrentAngle.real();
}
/**
* Generates the requested number of complex samples and returns them in a complex buffer
* @param sampleCount to generate
* @return complex buffer containing the requested complex samples
*/
public ComplexBuffer generateComplexBuffer(int sampleCount)
{
float[] buffer = new float[sampleCount * 2];
for(int x = 0; x < sampleCount; x++)
{
int index = 2 * x;
buffer[index] = inphase();
buffer[index + 1] = quadrature();
rotate();
}
return new ComplexBuffer(buffer);
}
}