diff --git a/src/dsp/filter/channelizer/ChannelDistributor.java b/src/dsp/filter/channelizer/ChannelDistributor.java
new file mode 100644
index 00000000..6bc5a84c
--- /dev/null
+++ b/src/dsp/filter/channelizer/ChannelDistributor.java
@@ -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
+ *
+ ******************************************************************************/
+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 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 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 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 listener)
+ {
+ mBroadcaster.addListener(listener);
+ }
+ }
+}
diff --git a/src/dsp/filter/channelizer/ChannelizerViewer.java b/src/dsp/filter/channelizer/ChannelizerViewer.java
index 94b8fb8c..17395e3e 100644
--- a/src/dsp/filter/channelizer/ChannelizerViewer.java
+++ b/src/dsp/filter/channelizer/ChannelizerViewer.java
@@ -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
+ public class ChannelArrayPanel extends JPanel implements Listener
{
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
+ {
+ 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()
{
diff --git a/src/dsp/filter/channelizer/PolyphaseChannelizer.java b/src/dsp/filter/channelizer/PolyphaseChannelizer.java
index a41e07fb..cd6eeda8 100644
--- a/src/dsp/filter/channelizer/PolyphaseChannelizer.java
+++ b/src/dsp/filter/channelizer/PolyphaseChannelizer.java
@@ -43,6 +43,7 @@ public class PolyphaseChannelizer implements Listener
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
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)
diff --git a/src/dsp/mixer/LowPhaseNoiseOscillator.java b/src/dsp/mixer/LowPhaseNoiseOscillator.java
new file mode 100644
index 00000000..fcbbf0c5
--- /dev/null
+++ b/src/dsp/mixer/LowPhaseNoiseOscillator.java
@@ -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
+ *
+ ******************************************************************************/
+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());
+ }
+}
diff --git a/src/dsp/mixer/Oscillator.java b/src/dsp/mixer/Oscillator.java
index 19736800..26c6ac6a 100644
--- a/src/dsp/mixer/Oscillator.java
+++ b/src/dsp/mixer/Oscillator.java
@@ -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);
+ }
}