mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-14 01:27:34 -05:00
wip-channelizer
This commit is contained in:
@@ -567,7 +567,7 @@ public class FilterFactory
|
||||
* For 40db attenuation, calculate the number of symbols based on the following formula:
|
||||
* Symbols = -44 * alpha + 33
|
||||
*
|
||||
* Polyphase Channelizer notes:
|
||||
* Polyphase TunerChannelizer notes:
|
||||
* -Set samples Per Symbol at 2 (or more) * number of channels
|
||||
* -Set symbolCount to sufficient size to produce required attenuation
|
||||
* -Set symbol rate in hertz
|
||||
@@ -725,7 +725,7 @@ public class FilterFactory
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a windowed-sync (Nyquist) M/2 prototype FIR filter for use with a Polyphase Channelizer/Synthesizer.
|
||||
* Creates a windowed-sync (Nyquist) M/2 prototype FIR filter for use with a Polyphase TunerChannelizer/Synthesizer.
|
||||
* The filter is designed for x2 oversampling of each channel and the filter cutoff frequency is incrementally
|
||||
* adjusted to achieve a -6.02 dB attenuation at the channel band edge to enable perfect reconstruction of adjacent
|
||||
* channels.
|
||||
@@ -833,7 +833,7 @@ public class FilterFactory
|
||||
|
||||
if(logResults)
|
||||
{
|
||||
mLog.debug("Polyphase Channelizer Filter Design Summary");
|
||||
mLog.debug("Polyphase TunerChannelizer Filter Design Summary");
|
||||
mLog.debug("-------------------------------------------");
|
||||
mLog.debug("Channels: " + channels);
|
||||
mLog.debug("Taps Per Channel: " + tapsPerChannel);
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/*******************************************************************************
|
||||
* 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 source.ISourceEventListener;
|
||||
import source.SourceEvent;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public abstract class AbstractComplexPolyphaseChannelizer implements Listener<ComplexBuffer>, ISourceEventListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AbstractComplexPolyphaseChannelizer.class);
|
||||
|
||||
private Broadcaster<SourceEvent> mSourceChangeBroadcaster = new Broadcaster();
|
||||
private List<PolyphaseChannelSource> mChannels = new CopyOnWriteArrayList<>();
|
||||
private int mChannelCount;
|
||||
private int mChannelSampleRate;
|
||||
|
||||
/**
|
||||
* Complex sample polyphase channelizer
|
||||
*
|
||||
* @param channelCount
|
||||
*/
|
||||
public AbstractComplexPolyphaseChannelizer(int channelCount, int channelSampleRate)
|
||||
{
|
||||
mChannelCount = channelCount;
|
||||
mChannelSampleRate = channelSampleRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of channels being processed by this channelizer.
|
||||
*/
|
||||
public int getChannelCount()
|
||||
{
|
||||
return mChannelCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output sample rate for each channel
|
||||
* @return sample rate in hertz
|
||||
*/
|
||||
public int getChannelSampleRate()
|
||||
{
|
||||
return mChannelSampleRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the complex sample pair
|
||||
*
|
||||
* @param inphase sample
|
||||
* @param quadrature sample
|
||||
*/
|
||||
protected abstract void filter(float inphase, float quadrature);
|
||||
|
||||
@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]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches the processed channel samples to any registered polyphase channel outputs.
|
||||
*
|
||||
* @param channels containing an array of I/Q samples per channel
|
||||
*/
|
||||
protected void dispatch(float[] channels)
|
||||
{
|
||||
for(PolyphaseChannelSource channel : mChannels)
|
||||
{
|
||||
channel.processChannels(channels);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the polyphase channel source to receive processed output channel samples
|
||||
*
|
||||
* @param polyphaseChannelSource
|
||||
*/
|
||||
public void addChannel(PolyphaseChannelSource polyphaseChannelSource)
|
||||
{
|
||||
mChannels.add(polyphaseChannelSource);
|
||||
mSourceChangeBroadcaster.addListener(polyphaseChannelSource.getSourceEventListener());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the polyphase channel source from receiving output channel samples.
|
||||
*
|
||||
* @param polyphaseChannelSource
|
||||
*/
|
||||
public void removeChannel(PolyphaseChannelSource polyphaseChannelSource)
|
||||
{
|
||||
mChannels.remove(polyphaseChannelSource);
|
||||
mSourceChangeBroadcaster.removeListener(polyphaseChannelSource.getSourceEventListener());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<SourceEvent> getSourceEventListener()
|
||||
{
|
||||
return mSourceChangeBroadcaster;
|
||||
}
|
||||
}
|
||||
135
src/dsp/filter/channelizer/Channelizer.java
Normal file
135
src/dsp/filter/channelizer/Channelizer.java
Normal file
@@ -0,0 +1,135 @@
|
||||
/*******************************************************************************
|
||||
* 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.Listener;
|
||||
import source.ISourceEventProcessor;
|
||||
import source.Source;
|
||||
import source.SourceEvent;
|
||||
import source.tuner.Tuner;
|
||||
import source.tuner.TunerChannel;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public class Channelizer implements ISourceEventProcessor, Listener<SourceEvent>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(Channelizer.class);
|
||||
|
||||
private Tuner mTuner;
|
||||
private List<PolyphaseChannelSource> mChannelSources = new CopyOnWriteArrayList<>();
|
||||
|
||||
/**
|
||||
* Provides access to Digital Drop Channel (DDC) sources from the tuner. Incorporates a polyphase channelizer
|
||||
* with downstream channel and upstream tuner management.
|
||||
*
|
||||
* @param tuner providing broadband sample buffers
|
||||
*/
|
||||
public Channelizer(Tuner tuner)
|
||||
{
|
||||
mTuner = tuner;
|
||||
mTuner.addSourceEventListener(this);
|
||||
}
|
||||
|
||||
public Source getChannel(TunerChannel tunerChannel)
|
||||
{
|
||||
//TODO: return a polyphase channel here
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts/adds the channel source to receive channelized sample buffers, registering with the tuner to receive
|
||||
* sample buffers when this is the first channel.
|
||||
*
|
||||
* @param channelSource to start
|
||||
*/
|
||||
private void startChannelSource(PolyphaseChannelSource channelSource)
|
||||
{
|
||||
if(mChannelSources.isEmpty())
|
||||
{
|
||||
//TODO: start the tuner
|
||||
}
|
||||
|
||||
mChannelSources.add(channelSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops/removes the channel source from receiving channelized sample buffers and deregisters from the tuner
|
||||
* if this is the last channel being sourced.
|
||||
*
|
||||
* @param channelSource to stop
|
||||
*/
|
||||
private void stopChannelSource(PolyphaseChannelSource channelSource)
|
||||
{
|
||||
mChannelSources.remove(channelSource);
|
||||
|
||||
if(mChannelSources.isEmpty())
|
||||
{
|
||||
//TODO: stop the tuner
|
||||
}
|
||||
|
||||
if(mTuner != null)
|
||||
{
|
||||
//TODO: change the tuner to accept a remove(TunerChannel) method
|
||||
// mTuner.remove(channel.getTunerChannel());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(SourceEvent sourceEvent)
|
||||
{
|
||||
switch(sourceEvent.getEvent())
|
||||
{
|
||||
case REQUEST_START_SAMPLE_STREAM:
|
||||
if(sourceEvent.hasSource() && sourceEvent.getSource() instanceof PolyphaseChannelSource)
|
||||
{
|
||||
startChannelSource((PolyphaseChannelSource)sourceEvent.getSource());
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Request to stop sample stream for unrecognized source: " +
|
||||
(sourceEvent.hasSource() ? sourceEvent.getSource().getClass() : "null source"));
|
||||
}
|
||||
//TODO: add the channel and register for samples from the tuner
|
||||
break;
|
||||
case REQUEST_STOP_SAMPLE_STREAM:
|
||||
if(sourceEvent.hasSource() && sourceEvent.getSource() instanceof PolyphaseChannelSource)
|
||||
{
|
||||
stopChannelSource((PolyphaseChannelSource)sourceEvent.getSource());
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Request to stop sample stream for unrecognized source: " +
|
||||
(sourceEvent.hasSource() ? sourceEvent.getSource().getClass() : "null source"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(SourceEvent sourceEvent)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ package dsp.filter.channelizer;
|
||||
|
||||
import dsp.filter.FilterFactory;
|
||||
import dsp.filter.Window;
|
||||
import dsp.filter.correction.IQCorrectionFilter;
|
||||
import dsp.filter.design.FilterDesignException;
|
||||
import dsp.mixer.LowPhaseNoiseOscillator;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
@@ -31,7 +30,7 @@ import sample.SampleType;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import settings.SettingsManager;
|
||||
import source.tuner.configuration.TunerConfigurationModel;
|
||||
import source.tuner.frequency.FrequencyChangeEvent;
|
||||
import source.SourceEvent;
|
||||
import spectrum.DFTProcessor;
|
||||
import spectrum.DFTSize;
|
||||
import spectrum.SpectrumPanel;
|
||||
@@ -86,7 +85,7 @@ public class ChannelizerViewer extends JFrame
|
||||
|
||||
private void init()
|
||||
{
|
||||
setTitle("Channelizer Viewer");
|
||||
setTitle("TunerChannelizer Viewer");
|
||||
setSize(1000, 800);
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||
@@ -212,7 +211,7 @@ public class ChannelizerViewer extends JFrame
|
||||
|
||||
public class ChannelArrayPanel extends JPanel implements Listener<ComplexBuffer>
|
||||
{
|
||||
private ComplexPolyphaseChannelizerM2 mPolyphaseChannelizer;
|
||||
private ComplexPolyphaseChannelizer mPolyphaseChannelizer;
|
||||
private ChannelDistributor mChannelDistributor;
|
||||
|
||||
public ChannelArrayPanel(float[] taps)
|
||||
@@ -223,7 +222,7 @@ public class ChannelizerViewer extends JFrame
|
||||
bufferSize++;
|
||||
}
|
||||
|
||||
mPolyphaseChannelizer = new ComplexPolyphaseChannelizerM2(taps, mChannelCount);
|
||||
mPolyphaseChannelizer = new ComplexPolyphaseChannelizer(taps, mChannelCount, CHANNEL_BANDWIDTH);
|
||||
mChannelDistributor = new ChannelDistributor(bufferSize, mChannelCount);
|
||||
mPolyphaseChannelizer.setChannelDistributor(mChannelDistributor);
|
||||
|
||||
@@ -272,8 +271,7 @@ public class ChannelizerViewer extends JFrame
|
||||
add(mSpectrumPanel);
|
||||
|
||||
mDFTProcessor.addConverter(mComplexDecibelConverter);
|
||||
mDFTProcessor.frequencyChanged(new FrequencyChangeEvent(
|
||||
FrequencyChangeEvent.Event.NOTIFICATION_SAMPLE_RATE_CHANGE, sampleRate));
|
||||
mDFTProcessor.process(SourceEvent.sampleRateChange(sampleRate));
|
||||
mComplexDecibelConverter.addListener(mSpectrumPanel);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,44 +25,40 @@ 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 ComplexPolyphaseChannelizer implements Listener<ComplexBuffer>
|
||||
public class ComplexPolyphaseChannelizer extends AbstractComplexPolyphaseChannelizer
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ComplexPolyphaseChannelizer.class);
|
||||
|
||||
private RealFIRFilter[] mInphaseFilters;
|
||||
private RealFIRFilter[] mQuadratureFilters;
|
||||
private int mFilterPointer = 0;
|
||||
private int mChannelCount;
|
||||
private float[] mFilteredSamples;
|
||||
private FloatFFT_1D mFFT;
|
||||
private ChannelDistributor mChannelDistributor;
|
||||
|
||||
/**
|
||||
* Maximally Decimated Polyphase Filter Bank (MDPFB) channelizer that divides the input frequency band into
|
||||
* equal bandwidth channels.
|
||||
*
|
||||
* @param taps of a low-pass filter designed for the inbound sample rate with a cutoff frequency
|
||||
* @param coefficients of a prototype 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
|
||||
* filter 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 channels to output.
|
||||
* @param channelCount - number of channels to output.
|
||||
* @param channelSampleRate - channel sample rate (after decimation)
|
||||
*/
|
||||
public ComplexPolyphaseChannelizer(float[] taps, int channels)
|
||||
public ComplexPolyphaseChannelizer(float[] coefficients, int channelCount, int channelSampleRate)
|
||||
{
|
||||
mChannelCount = channels;
|
||||
super(channelCount, channelSampleRate);
|
||||
|
||||
initFilters(taps);
|
||||
initFilters(coefficients);
|
||||
}
|
||||
|
||||
public void filter(float inphase, float quadrature)
|
||||
@Override
|
||||
protected void filter(float inphase, float quadrature)
|
||||
{
|
||||
int index = (mChannelCount - mFilterPointer - 1) * 2;
|
||||
int index = (getChannelCount() - mFilterPointer - 1) * 2;
|
||||
|
||||
mFilteredSamples[index] = mInphaseFilters[mFilterPointer].filter(inphase);
|
||||
mFilteredSamples[index + 1] = mQuadratureFilters[mFilterPointer].filter(quadrature);
|
||||
@@ -70,7 +66,7 @@ public class ComplexPolyphaseChannelizer implements Listener<ComplexBuffer>
|
||||
if(mFilterPointer == 0)
|
||||
{
|
||||
calculate();
|
||||
mFilterPointer = mChannelCount - 1;
|
||||
mFilterPointer = getChannelCount() - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -78,20 +74,9 @@ public class ComplexPolyphaseChannelizer implements Listener<ComplexBuffer>
|
||||
}
|
||||
}
|
||||
|
||||
@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];
|
||||
float[] samples = new float[getChannelCount() * 2];
|
||||
System.arraycopy(mFilteredSamples, 0, samples, 0, mFilteredSamples.length);
|
||||
|
||||
//FFT is executed in-place where the output overwrites the input
|
||||
@@ -100,24 +85,6 @@ public class ComplexPolyphaseChannelizer implements Listener<ComplexBuffer>
|
||||
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
|
||||
*
|
||||
@@ -125,15 +92,15 @@ public class ComplexPolyphaseChannelizer implements Listener<ComplexBuffer>
|
||||
*/
|
||||
private void initFilters(float[] taps)
|
||||
{
|
||||
int tapCount = (int)Math.ceil((double)taps.length / (double)mChannelCount);
|
||||
int tapCount = (int)Math.ceil((double)taps.length / (double)getChannelCount());
|
||||
|
||||
float[][] coefficients = new float[mChannelCount][tapCount];
|
||||
float[][] coefficients = new float[getChannelCount()][tapCount];
|
||||
|
||||
for(int tap = 0; tap < tapCount; tap++)
|
||||
{
|
||||
for(int filter = 0; filter < mChannelCount; filter++)
|
||||
for(int filter = 0; filter < getChannelCount(); filter++)
|
||||
{
|
||||
int index = tap * mChannelCount + filter;
|
||||
int index = tap * getChannelCount() + filter;
|
||||
|
||||
if(index < taps.length)
|
||||
{
|
||||
@@ -142,18 +109,18 @@ public class ComplexPolyphaseChannelizer implements Listener<ComplexBuffer>
|
||||
}
|
||||
}
|
||||
|
||||
mInphaseFilters = new RealFIRFilter[mChannelCount];
|
||||
mQuadratureFilters = new RealFIRFilter[mChannelCount];
|
||||
mInphaseFilters = new RealFIRFilter[getChannelCount()];
|
||||
mQuadratureFilters = new RealFIRFilter[getChannelCount()];
|
||||
|
||||
for(int filter = 0; filter < mChannelCount; filter++)
|
||||
for(int filter = 0; filter < getChannelCount(); 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;
|
||||
mFilteredSamples = new float[getChannelCount() * 2];
|
||||
mFFT = new FloatFFT_1D(getChannelCount());
|
||||
mFilterPointer = getChannelCount() -1;
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
@@ -171,14 +138,14 @@ public class ComplexPolyphaseChannelizer implements Listener<ComplexBuffer>
|
||||
int channelCenter = (int)(12500 * 1.5);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("\nPolyphase Channelizer\n");
|
||||
sb.append("\nPolyphase TunerChannelizer\n");
|
||||
sb.append("Sample Rate:" + sampleRate + " Channels:" + channels + " Channel Rate:" + channelBandwidth + "\n");
|
||||
sb.append("Tap Count:" + taps.length + "\n");
|
||||
sb.append("Channel:" + channelCenter);
|
||||
|
||||
mLog.debug(sb.toString());
|
||||
|
||||
ComplexPolyphaseChannelizer channelizer = new ComplexPolyphaseChannelizer(taps, 8);
|
||||
ComplexPolyphaseChannelizer channelizer = new ComplexPolyphaseChannelizer(taps, 8, channelBandwidth);
|
||||
|
||||
Oscillator oscillator = new Oscillator(channelCenter, 2 * sampleRate);
|
||||
|
||||
|
||||
@@ -25,21 +25,18 @@ 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 ComplexPolyphaseChannelizerM2 implements Listener<ComplexBuffer>
|
||||
public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChannelizer
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ComplexPolyphaseChannelizerM2.class);
|
||||
|
||||
private ReverseFloatCircularBuffer[] mDataBuffers;
|
||||
private float[][] mFilterCoefficients;
|
||||
private int mFilterPointer = 0;
|
||||
private int mChannelCount;
|
||||
private FloatFFT_1D mFFT;
|
||||
private ChannelDistributor mChannelDistributor;
|
||||
|
||||
private DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0000");
|
||||
|
||||
@@ -51,23 +48,23 @@ public class ComplexPolyphaseChannelizerM2 implements Listener<ComplexBuffer>
|
||||
* 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).
|
||||
* @param channelSampleRate for each decimated, oversampled channel
|
||||
*/
|
||||
public ComplexPolyphaseChannelizerM2(float[] taps, int channels)
|
||||
public ComplexPolyphaseChannelizerM2(float[] taps, int channels, int channelSampleRate)
|
||||
{
|
||||
super(channels, channelSampleRate);
|
||||
|
||||
if(channels % 2 != 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Channel count must be a multiple of 2");
|
||||
}
|
||||
|
||||
mChannelCount = channels;
|
||||
|
||||
initFilters(taps);
|
||||
}
|
||||
|
||||
public void filter(float inphase, float quadrature)
|
||||
protected void filter(float inphase, float quadrature)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -77,9 +74,9 @@ public class ComplexPolyphaseChannelizerM2 implements Listener<ComplexBuffer>
|
||||
if(mFilterPointer == 0)
|
||||
{
|
||||
calculate();
|
||||
mFilterPointer = mChannelCount * 2;
|
||||
mFilterPointer = getChannelCount() * 2;
|
||||
}
|
||||
else if(mFilterPointer == mChannelCount)
|
||||
else if(mFilterPointer == getChannelCount())
|
||||
{
|
||||
calculate();
|
||||
}
|
||||
@@ -92,20 +89,9 @@ public class ComplexPolyphaseChannelizerM2 implements Listener<ComplexBuffer>
|
||||
}
|
||||
}
|
||||
|
||||
@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];
|
||||
float[] samples = new float[getChannelCount() * 2];
|
||||
|
||||
if(mFilterPointer == 0)
|
||||
{
|
||||
@@ -158,6 +144,7 @@ public class ComplexPolyphaseChannelizerM2 implements Listener<ComplexBuffer>
|
||||
|
||||
/**
|
||||
* Filters the samples contained in the buffer against the filter coefficients
|
||||
*
|
||||
* @param coefficients of the sub filter
|
||||
* @param buffer containing data samples
|
||||
* @return
|
||||
@@ -166,32 +153,14 @@ public class ComplexPolyphaseChannelizerM2 implements Listener<ComplexBuffer>
|
||||
{
|
||||
float accumulator = 0.0f;
|
||||
|
||||
for( int x = 0; x < coefficients.length; x++ )
|
||||
for(int x = 0; x < coefficients.length; x++)
|
||||
{
|
||||
accumulator += coefficients[ x ] * buffer.get(x);
|
||||
accumulator += coefficients[x] * buffer.get(x);
|
||||
}
|
||||
|
||||
return accumulator;
|
||||
}
|
||||
|
||||
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
|
||||
*
|
||||
@@ -199,22 +168,22 @@ public class ComplexPolyphaseChannelizerM2 implements Listener<ComplexBuffer>
|
||||
*/
|
||||
private void initFilters(float[] taps)
|
||||
{
|
||||
int tapCount = (int)Math.ceil((double)taps.length / (double)mChannelCount);
|
||||
int tapCount = (int)Math.ceil((double)taps.length / (double)getChannelCount());
|
||||
|
||||
mDataBuffers = new ReverseFloatCircularBuffer[mChannelCount * 2];
|
||||
mDataBuffers = new ReverseFloatCircularBuffer[getChannelCount() * 2];
|
||||
|
||||
for(int x = 0; x < mChannelCount * 2; x++)
|
||||
for(int x = 0; x < getChannelCount() * 2; x++)
|
||||
{
|
||||
mDataBuffers[x] = new ReverseFloatCircularBuffer(tapCount);
|
||||
}
|
||||
|
||||
mFilterCoefficients = new float[mChannelCount][tapCount];
|
||||
mFilterCoefficients = new float[getChannelCount()][tapCount];
|
||||
|
||||
for(int tap = 0; tap < tapCount; tap++)
|
||||
{
|
||||
for(int filter = 0; filter < mChannelCount; filter++)
|
||||
for(int filter = 0; filter < getChannelCount(); filter++)
|
||||
{
|
||||
int index = tap * mChannelCount + filter;
|
||||
int index = tap * getChannelCount() + filter;
|
||||
|
||||
if(index < taps.length)
|
||||
{
|
||||
@@ -223,8 +192,8 @@ public class ComplexPolyphaseChannelizerM2 implements Listener<ComplexBuffer>
|
||||
}
|
||||
}
|
||||
|
||||
mFFT = new FloatFFT_1D(mChannelCount);
|
||||
mFilterPointer = mChannelCount - 1; //Start at M/2
|
||||
mFFT = new FloatFFT_1D(getChannelCount());
|
||||
mFilterPointer = getChannelCount() - 1; //Start at M/2
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
@@ -237,19 +206,19 @@ public class ComplexPolyphaseChannelizerM2 implements Listener<ComplexBuffer>
|
||||
int channelBandwidth = 12500;
|
||||
int sampleRate = channels * channelBandwidth;
|
||||
|
||||
float[] taps = FilterFactory.getLowPass( 10000000, 12500, 5600, Window.WindowType.BLACKMAN_HARRIS_7 );
|
||||
float[] taps = FilterFactory.getLowPass(10000000, 12500, 5600, Window.WindowType.BLACKMAN_HARRIS_7);
|
||||
|
||||
int channelCenter = (int)(12500 * 1.5);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("\nPolyphase Channelizer\n");
|
||||
sb.append("\nPolyphase TunerChannelizer\n");
|
||||
sb.append("Sample Rate:" + sampleRate + " Channels:" + channels + " Channel Rate:" + channelBandwidth + "\n");
|
||||
sb.append("Tap Count:" + taps.length + "\n");
|
||||
sb.append("Channel:" + channelCenter);
|
||||
|
||||
mLog.debug(sb.toString());
|
||||
|
||||
ComplexPolyphaseChannelizerM2 channelizer = new ComplexPolyphaseChannelizerM2(taps, 8);
|
||||
ComplexPolyphaseChannelizerM2 channelizer = new ComplexPolyphaseChannelizerM2(taps, 8, channelBandwidth * 2);
|
||||
|
||||
Oscillator oscillator = new Oscillator(channelCenter, 2 * sampleRate);
|
||||
|
||||
|
||||
201
src/dsp/filter/channelizer/PolyphaseChannelSource.java
Normal file
201
src/dsp/filter/channelizer/PolyphaseChannelSource.java
Normal file
@@ -0,0 +1,201 @@
|
||||
/*******************************************************************************
|
||||
* 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.Listener;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import source.ISourceEventProcessor;
|
||||
import source.SourceEvent;
|
||||
import source.SourceException;
|
||||
import source.tuner.ComplexChannelSource;
|
||||
import source.tuner.TunerChannel;
|
||||
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
public class PolyphaseChannelSource extends ComplexChannelSource
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class);
|
||||
|
||||
private TunerChannel mTunerChannel;
|
||||
private long mTunerCenterFrequency;
|
||||
private int mUpstreamSampleRate;
|
||||
private int mChannelSampleRate;
|
||||
private int mChannelCount;
|
||||
private int mOutputFrequencyCorrection;
|
||||
|
||||
/**
|
||||
* Polyphase channelizer channel implementation. Adapts the channel array output samples from the polyphase
|
||||
* channelizer into a single channel, or a channel synthesized from two adjacent channels that is frequency
|
||||
* translated and decimated to a single channel.
|
||||
*
|
||||
* @param tunerChannel - requested output tuner channel frequency and bandwidth.
|
||||
*/
|
||||
public PolyphaseChannelSource(ISourceEventProcessor upstreamSourceEventProcessor, TunerChannel tunerChannel)
|
||||
{
|
||||
super(upstreamSourceEventProcessor);
|
||||
|
||||
mTunerChannel = tunerChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process source event notifications received from upstream source provider and source event requests
|
||||
* received from downstream consumers.
|
||||
*
|
||||
* @param sourceEvent containing a request or notification
|
||||
* @throws SourceException if there are any errors while processing the source event
|
||||
*/
|
||||
@Override
|
||||
public void process(SourceEvent sourceEvent) throws SourceException
|
||||
{
|
||||
switch(sourceEvent.getEvent())
|
||||
{
|
||||
case NOTIFICATION_CHANNEL_SAMPLE_RATE_CHANGE:
|
||||
setChannelSampleRate(sourceEvent.getValue().intValue());
|
||||
break;
|
||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||
mTunerCenterFrequency = sourceEvent.getValue().longValue();
|
||||
updateInputConfiguration();
|
||||
break;
|
||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||
setUpstreamSampleRate(sourceEvent.getValue().intValue());
|
||||
updateInputConfiguration();
|
||||
break;
|
||||
case REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
|
||||
setFrequencyCorrection(sourceEvent.getValue().intValue());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the array samples for each of the output channels from the polyphase channelizer.
|
||||
* @param channels
|
||||
*/
|
||||
public void processChannels(float[] channels)
|
||||
{
|
||||
|
||||
//TODO we have to dispatch full buffers on a separate thread so that we don't bog down the channelizer
|
||||
//TODO thread that is delivering the channel samples to us.
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates current processing configuration any time there is a change in tuner center frequency
|
||||
* or tuner sample rate.
|
||||
*/
|
||||
private void updateInputConfiguration()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Downstream sample rate
|
||||
*
|
||||
* @return sample rate in Hertz
|
||||
* @throws SourceException if this method is accessed before the channel count is established
|
||||
*/
|
||||
@Override
|
||||
public int getSampleRate() throws SourceException
|
||||
{
|
||||
if(mChannelSampleRate > 0)
|
||||
{
|
||||
return mChannelSampleRate;
|
||||
}
|
||||
|
||||
throw new SourceException("Channel sample rate has not been set.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the downstream channel sample rate. Since oversampling may occur in the upstream channelizer, we can't
|
||||
* rely on simply dividing the upstream sample rate by the number of channels. Thus, we required the upstream
|
||||
* channelizer to tell us the exact channel sample rate.
|
||||
*
|
||||
* @param channelSampleRate in hertz
|
||||
*/
|
||||
private void setChannelSampleRate(int channelSampleRate)
|
||||
{
|
||||
mChannelSampleRate = channelSampleRate;
|
||||
|
||||
//Translate the upstream channel sample rate event into a downstream sample rate change event
|
||||
broadcast(SourceEvent.sampleRateChange(mChannelSampleRate));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the input sample rate and broadcasts a source change event to the downstream listener
|
||||
* @param sampleRate from the upstream source
|
||||
*/
|
||||
private void setUpstreamSampleRate(int sampleRate)
|
||||
{
|
||||
mUpstreamSampleRate = sampleRate;
|
||||
|
||||
try
|
||||
{
|
||||
broadcast(SourceEvent.sampleRateChange(getSampleRate()));
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
mLog.error("Upstream sample rate changed - couldn't broadcast channel sample rate change event " +
|
||||
"to downstream channel", se);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the polyphase channel count.
|
||||
* @param channelCount number of channels being processed by the polyphase channelizer
|
||||
*/
|
||||
private void setChannelCount(int channelCount)
|
||||
{
|
||||
mChannelCount = channelCount;
|
||||
|
||||
try
|
||||
{
|
||||
broadcast(SourceEvent.sampleRateChange(getSampleRate()));
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
mLog.error("Polyphase channel count changed - couldn't broadcast channel sample rate change event " +
|
||||
"to downstream channel", se);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFrequency() throws SourceException
|
||||
{
|
||||
return mTunerChannel.getFrequency();
|
||||
}
|
||||
|
||||
private void setFrequencyCorrection(int correction)
|
||||
{
|
||||
//TODO:
|
||||
mLog.debug("Request for frequency correction: " + correction + " -- not yet implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -151,7 +151,7 @@ public class FIRFilterSpecification
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Polyphase Channelizer filter specification builder that allows you to define the filter parameters and
|
||||
* Creates a Polyphase TunerChannelizer filter specification builder that allows you to define the filter parameters and
|
||||
* create a filter specification
|
||||
*/
|
||||
public static ChannelizerBuilder channelizerBuilder()
|
||||
|
||||
@@ -23,7 +23,7 @@ import dsp.filter.fir.FIRFilterSpecification;
|
||||
public class PolyphaseChannelizerDesigner extends FIRFilterSpecification
|
||||
{
|
||||
/**
|
||||
* Constructs a Polyphase Channelizer Remez filter specification.
|
||||
* Constructs a Polyphase TunerChannelizer Remez filter specification.
|
||||
*
|
||||
* Constructor is package private -- use the PolyphaseChannelizerFilterFactory to create a filter
|
||||
*
|
||||
|
||||
@@ -43,7 +43,7 @@ import java.util.List;
|
||||
import sample.Listener;
|
||||
import sample.complex.Complex;
|
||||
import sample.complex.ComplexSampleListener;
|
||||
import source.tuner.frequency.IFrequencyChangeListener;
|
||||
import source.ISourceEventListener;
|
||||
import buffer.FloatAveragingBuffer;
|
||||
import dsp.filter.interpolator.RealInterpolator;
|
||||
import dsp.symbol.Dibit;
|
||||
@@ -73,7 +73,7 @@ public class LSMDemodulator implements Instrumentable, ComplexSampleListener
|
||||
|
||||
private CostasLoop mCostasLoop = new CostasLoop();
|
||||
|
||||
private IFrequencyChangeListener mFrequencyChangeListener;
|
||||
private ISourceEventListener mFrequencyChangeListener;
|
||||
|
||||
private EyeDiagramDataTap mEyeDiagramDataTap;
|
||||
|
||||
@@ -147,12 +147,12 @@ public class LSMDemodulator implements Instrumentable, ComplexSampleListener
|
||||
}
|
||||
}
|
||||
|
||||
public void addListener( IFrequencyChangeListener listener )
|
||||
public void addListener( ISourceEventListener listener )
|
||||
{
|
||||
mFrequencyChangeListener = listener;
|
||||
}
|
||||
|
||||
public void removeListener( IFrequencyChangeListener listener )
|
||||
public void removeListener( ISourceEventListener listener )
|
||||
{
|
||||
mFrequencyChangeListener = null;
|
||||
}
|
||||
@@ -202,8 +202,8 @@ public class LSMDemodulator implements Instrumentable, ComplexSampleListener
|
||||
// if( correctionNeeded && mFrequencyChangeListener != null )
|
||||
// {
|
||||
// mLog.debug( "Issuing Frequency Correction: " + mFrequencyError );
|
||||
// mFrequencyChangeListener.frequencyChanged(
|
||||
// new FrequencyChangeEvent( Attribute.FREQUENCY_ERROR,
|
||||
// mFrequencyChangeListener.process(
|
||||
// new SourceEvent( Attribute.FREQUENCY_ERROR,
|
||||
// mFrequencyError ) );
|
||||
// }
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user