diff --git a/src/dsp/filter/channelizer/ChannelDistributor.java b/src/dsp/filter/channelizer/ChannelDistributor.java index 6bc5a84c..a3cca8ac 100644 --- a/src/dsp/filter/channelizer/ChannelDistributor.java +++ b/src/dsp/filter/channelizer/ChannelDistributor.java @@ -26,6 +26,7 @@ import sample.complex.ComplexBuffer; import java.util.HashMap; import java.util.Map; +import java.util.Queue; public class ChannelDistributor { @@ -85,7 +86,7 @@ public class ChannelDistributor public void receive(float inphase, float quadrature) { mBuffer[mBufferPointer++] = inphase; - mBuffer[mBufferPointer++] = inphase; + mBuffer[mBufferPointer++] = quadrature; if(mBufferPointer >= mBufferSize) { diff --git a/src/dsp/filter/channelizer/ChannelizerViewer.java b/src/dsp/filter/channelizer/ChannelizerViewer.java index 17395e3e..45bba720 100644 --- a/src/dsp/filter/channelizer/ChannelizerViewer.java +++ b/src/dsp/filter/channelizer/ChannelizerViewer.java @@ -96,7 +96,7 @@ public class ChannelizerViewer extends JFrame private void generateSamples() { - int size = mChannelCount * CHANNEL_BANDWIDTH / CHANNEL_FFT_FRAME_RATE * 2; + int size = mSampleRate / CHANNEL_FFT_FRAME_RATE; float[] samples = mOscillator.generate(size); @@ -137,10 +137,13 @@ public class ChannelizerViewer extends JFrame if(mControlPanel == null) { mControlPanel = new JPanel(); + mControlPanel.setLayout(new MigLayout("insets 0 0 0 0", "", "")); + mControlPanel.add(new JLabel("Tone:"), "align left"); int maximumFrequency = mSampleRate / 2; - SpinnerNumberModel model = new SpinnerNumberModel(mToneFrequency, -maximumFrequency, maximumFrequency, CHANNEL_BANDWIDTH ); + SpinnerNumberModel model = new SpinnerNumberModel(mToneFrequency, -maximumFrequency, maximumFrequency, + 500 ); model.addChangeListener(new ChangeListener() { @@ -155,6 +158,12 @@ public class ChannelizerViewer extends JFrame JSpinner spinner = new JSpinner(model); mControlPanel.add(spinner); + mControlPanel.add(new JLabel("Hz")); + + mControlPanel.add(new JLabel("Sample Rate: " + mSampleRate + " Hz"), "push,align right"); + + mControlPanel.add(new JLabel("Max: " + (mSampleRate / 2) + " Hz"), "push,align right"); + mControlPanel.add(new JLabel("Channels: " + mChannelCount), "push,align right"); } return mControlPanel; @@ -174,7 +183,7 @@ public class ChannelizerViewer extends JFrame { int symbolRate = 4800; int samplesPerSymbol = 2; - int symbolCount = 8; + int symbolCount = 14; //Alpha is the residual channel bandwidth left over from the symbol rate and samples per symbol float alpha = ((float)CHANNEL_BANDWIDTH / (float)(symbolRate * samplesPerSymbol)) - 1.0f; @@ -193,7 +202,7 @@ public class ChannelizerViewer extends JFrame public class ChannelArrayPanel extends JPanel implements Listener { - private PolyphaseChannelizer mPolyphaseChannelizer; + private PolyphaseChannelizer2 mPolyphaseChannelizer; private ChannelDistributor mChannelDistributor; public ChannelArrayPanel(float[] taps) @@ -204,7 +213,7 @@ public class ChannelizerViewer extends JFrame bufferSize++; } - mPolyphaseChannelizer = new PolyphaseChannelizer(taps, mChannelCount); + mPolyphaseChannelizer = new PolyphaseChannelizer2(taps, mChannelCount); mChannelDistributor = new ChannelDistributor(bufferSize, mChannelCount); mPolyphaseChannelizer.setChannelDistributor(mChannelDistributor); @@ -213,21 +222,23 @@ public class ChannelizerViewer extends JFrame private void init() { - setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill][grow,fill][grow,fill][grow,fill]", "")); + setLayout(new MigLayout("insets 0 0 0 0", "fill", "fill")); + + int channelsPerRow = 32; for(int x = 0; x < mChannelCount; x++) { ChannelPanel channelPanel = new ChannelPanel(mSettingsManager, CHANNEL_BANDWIDTH * 2); - channelPanel.setPreferredSize(new Dimension(250,100)); +// channelPanel.setPreferredSize(new Dimension(250,100)); channelPanel.setDFTSize(DFTSize.FFT00512); - if(x % 4 == 3) + if(x % channelsPerRow == channelsPerRow - 1) { - add(channelPanel, "wrap"); + add(channelPanel, "grow,push,wrap 2px"); } else { - add(channelPanel); + add(channelPanel, "grow,push"); } mChannelDistributor.addListener(x, channelPanel); @@ -303,7 +314,7 @@ public class ChannelizerViewer extends JFrame public static void main(String[] args) { - int channelCount = 24; + int channelCount = 512; final ChannelizerViewer frame = new ChannelizerViewer(channelCount); diff --git a/src/dsp/filter/channelizer/PolyphaseChannelizer.java b/src/dsp/filter/channelizer/PolyphaseChannelizer.java index cd6eeda8..2390cb00 100644 --- a/src/dsp/filter/channelizer/PolyphaseChannelizer.java +++ b/src/dsp/filter/channelizer/PolyphaseChannelizer.java @@ -153,11 +153,11 @@ public class PolyphaseChannelizer implements Listener float[][] coefficients = new float[mChannelCount][tapCount]; - for(int filter = 0; filter < mChannelCount; filter++) + for(int tap = 0; tap < tapCount; tap++) { - for(int tap = 0; tap < tapCount; tap++) + for(int filter = 0; filter < mChannelCount; filter++) { - int index = filter * mChannelCount + tap; + int index = tap * mChannelCount + filter; if(index < taps.length) { diff --git a/src/dsp/filter/channelizer/PolyphaseChannelizer2.java b/src/dsp/filter/channelizer/PolyphaseChannelizer2.java new file mode 100644 index 00000000..1168b89f --- /dev/null +++ b/src/dsp/filter/channelizer/PolyphaseChannelizer2.java @@ -0,0 +1,202 @@ +/******************************************************************************* + * 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 dsp.filter.FilterFactory; +import dsp.filter.fir.real.RealFIRFilter; +import dsp.mixer.Oscillator; +import org.jtransforms.fft.FloatFFT_1D; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sample.Listener; +import sample.complex.ComplexBuffer; + +import java.text.DecimalFormat; + +public class PolyphaseChannelizer2 implements Listener +{ + private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelizer2.class); + + private RealFIRFilter[] mInphaseFilters; + private RealFIRFilter[] mQuadratureFilters; + private int mFilterPointer = 0; + private int mChannelCount; + private int mBlockSize; + private float[] mFilteredSamples; + private FloatFFT_1D mFFT; + private ChannelDistributor mChannelDistributor; + + private DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0000"); + + /** + * Creates a polyphase filter bank that divides the input frequency band into evenly spaced channels that + * are oversampled by 2x for output. + * + * @param taps of a low-pass filter designed for the inbound sample rate with a cutoff frequency + * equal to the channel bandwidth (sample rate / filters). If you need to synthesize (combine two or more + * filte outputs) a new bandwidth signal from the outputs of this filter, then the filter should be designed + * as a nyquist filter with -6 dB attenuation at the channel bandwidth cutoff frequency + * + * @param channels - number of filters/channels to output. Since this filter bank oversamples each filter + * output, this number must be even (divisible by the oversample rate). + */ + public PolyphaseChannelizer2(float[] taps, int channels) + { + assert(channels % 2 == 0); + + mChannelCount = channels; + + initFilters(taps); + } + + public void filter(float inphase, float quadrature) + { + int index = mFilterPointer * 2; + + mFilteredSamples[index] = mInphaseFilters[mFilterPointer].filter(inphase); + mFilteredSamples[index + 1] = mQuadratureFilters[mFilterPointer].filter(quadrature); + + if(mFilterPointer == 0) + { + calculate(); + mFilterPointer = mChannelCount - 1; + } + else + { + mFilterPointer--; + } + } + + @Override + public void receive(ComplexBuffer complexBuffer) + { + float[] samples = complexBuffer.getSamples(); + + for(int x = 0; x < samples.length; x += 2) + { + filter(samples[x], samples[x + 1]); + } + } + + private void calculate() + { + float[] samples = new float[mChannelCount * 2]; + System.arraycopy(mFilteredSamples, 0, samples, 0, mFilteredSamples.length); + + //FFT is executed in-place where the output overwrites the input + mFFT.complexForward(samples); + + dispatch(samples); + } + + private void dispatch(float[] channels) + { + if(mChannelDistributor != null) + { + mChannelDistributor.receive(channels); + } + } + + public void setChannelDistributor(ChannelDistributor channelDistributor) + { + mChannelDistributor = channelDistributor; + } + + private double magnitude(float real, float imaginary, int fftSize) + { + return Math.sqrt(Math.pow(real, 2.0) + Math.pow(imaginary, 2.0)); + } + + /** + * Distributes the filter taps to each of the polyphase filters + * + * @param taps + */ + private void initFilters(float[] taps) + { + int tapCount = (int)Math.ceil((double)taps.length / (double)mChannelCount); + + float[][] coefficients = new float[mChannelCount][tapCount]; + + for(int tap = 0; tap < tapCount; tap++) + { + for(int filter = 0; filter < mChannelCount; filter++) + { + int index = tap * mChannelCount + filter; + + if(index < taps.length) + { + coefficients[filter][tap] = taps[index]; + } + } + } + + mInphaseFilters = new RealFIRFilter[mChannelCount]; + mQuadratureFilters = new RealFIRFilter[mChannelCount]; + + for(int filter = 0; filter < mChannelCount; filter++) + { + mInphaseFilters[filter] = new RealFIRFilter(coefficients[filter], 1.0f); + mQuadratureFilters[filter] = new RealFIRFilter(coefficients[filter], 1.0f); + } + + mFilteredSamples = new float[mChannelCount * 2]; + mFFT = new FloatFFT_1D(mChannelCount); + mFilterPointer = mChannelCount -1; + } + + public static void main(String[] args) + { + mLog.debug("Starting ..."); + + int symbolRate = 4800; + int samplesPerSymbol = 2; + int symbolCount = 4; + int channels = 10; + int channelBandwidth = 12500; + int sampleRate = channels * channelBandwidth; + + //Alpha is the residual channel bandwidth left over from the symbol rate and samples per symbol + float alpha = ((float)channelBandwidth / (float)(symbolRate * samplesPerSymbol)) - 1.0f; + + float[] taps = FilterFactory.getRootRaisedCosine(samplesPerSymbol * channels, symbolCount, alpha); + + int channelCenter = (int)(12500 * 1.5); + + StringBuilder sb = new StringBuilder(); + sb.append("\nPolyphase Channelizer\n"); + sb.append("Sample Rate:" + sampleRate + " Channels:" + channels + " Channel Rate:" + channelBandwidth + "\n"); + sb.append("Alpha: " + alpha + " Tap Count:" + taps.length + "\n"); + sb.append("Channel:" + channelCenter); + + mLog.debug(sb.toString()); + + PolyphaseChannelizer2 channelizer = new PolyphaseChannelizer2(taps, 8); + + Oscillator oscillator = new Oscillator(channelCenter, 2 * sampleRate); + + for(int x = 0; x < 2000; x++) + { + channelizer.filter(oscillator.getComplex().inphase(), oscillator.getComplex().quadrature()); + oscillator.rotate(); + } + + mLog.debug("Finished!"); + } +} diff --git a/src/dsp/mixer/LowPhaseNoiseOscillator.java b/src/dsp/mixer/LowPhaseNoiseOscillator.java index fcbbf0c5..ad190e2d 100644 --- a/src/dsp/mixer/LowPhaseNoiseOscillator.java +++ b/src/dsp/mixer/LowPhaseNoiseOscillator.java @@ -20,6 +20,7 @@ package dsp.mixer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sample.complex.ComplexBuffer; import java.util.Arrays; @@ -111,16 +112,57 @@ public class LowPhaseNoiseOscillator public static void main(String[] args) { - LowPhaseNoiseOscillator o = new LowPhaseNoiseOscillator(10, 1); + int sampleRate = 10000000; + int frequency = 24000; + int sampleSize = 65536; + int warmUpIterations = 100000; + int testIterations = 500000; - float[] samples = o.generate(20); + LowPhaseNoiseOscillator o = new LowPhaseNoiseOscillator(sampleRate, frequency); - StringBuilder sb = new StringBuilder(); - for(int x = 0; x < samples.length; x += 2) + mLog.debug("Warming up ..."); + + for(int x = 0; x < warmUpIterations; x++) { - sb.append(samples[x]).append(" ").append(samples[x + 1]).append("\n"); + float[] samples = o.generate(sampleSize); } - mLog.debug("Samples:\n" + sb.toString()); + mLog.debug("Testing ..."); + + long start = System.nanoTime(); + + for(int x = 0; x < testIterations; x++) + { + float[] samples = o.generate(sampleSize); + } + + long duration = System.nanoTime() - start; + + double durationSeconds = (double)duration / 1000000000.0; + mLog.debug("New Took:" + durationSeconds + " seconds to generate " + (testIterations * sampleSize) + " samples"); + + Oscillator o2 = new Oscillator(frequency, sampleRate); + + mLog.debug("Warming up ..."); + + for(int x = 0; x < warmUpIterations; x++) + { + ComplexBuffer samples = o2.generateComplexBuffer(sampleSize); + } + + mLog.debug("Testing ..."); + + long start2 = System.nanoTime(); + + for(int x = 0; x < testIterations; x++) + { + float[] samples = o.generate(sampleSize); + } + + long duration2 = System.nanoTime() - start2; + + double durationSeconds2 = (double)duration2 / 1000000000.0; + mLog.debug("New Took:" + durationSeconds2 + " seconds to generate " + (testIterations * sampleSize) + " samples"); + } }