mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-13 09:07:27 -05:00
#39 WIP polyphase channelizer
Updates the M2 channelizer and correctly implements the ChannelCalculator
This commit is contained in:
@@ -749,14 +749,14 @@ public class FilterFactory
|
|||||||
*
|
*
|
||||||
* @throws FilterDesignException if a filter cannot be designed with a band edge attenuation of -6.02 dB
|
* @throws FilterDesignException if a filter cannot be designed with a band edge attenuation of -6.02 dB
|
||||||
*/
|
*/
|
||||||
public static float[] getSincChannelizer(int channelBandwidth, int channels, int tapsPerChannel, WindowType windowType,
|
public static float[] getSincChannelizer(double channelBandwidth, int channels, int tapsPerChannel,
|
||||||
boolean logResults) throws FilterDesignException
|
WindowType windowType, boolean logResults) throws FilterDesignException
|
||||||
{
|
{
|
||||||
int currentTapsPerChannel = tapsPerChannel;
|
int currentTapsPerChannel = tapsPerChannel;
|
||||||
int sampleRate = channelBandwidth * channels * 2;
|
int sampleRate = (int)(channelBandwidth * (double)channels * 2.0);
|
||||||
int filterLength = (channels * currentTapsPerChannel) - 1;
|
int filterLength = (channels * currentTapsPerChannel) - 1;
|
||||||
|
|
||||||
double cutoffFrequency = ((double)channelBandwidth / (double)sampleRate);
|
double cutoffFrequency = channelBandwidth / (double)sampleRate;
|
||||||
double bandEdge = cutoffFrequency;
|
double bandEdge = cutoffFrequency;
|
||||||
double increment = cutoffFrequency * 0.1;
|
double increment = cutoffFrequency * 0.1;
|
||||||
|
|
||||||
@@ -831,6 +831,10 @@ public class FilterFactory
|
|||||||
throw new FilterDesignException("Cannot design filter to specifications");
|
throw new FilterDesignException("Cannot design filter to specifications");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//This is an odd length filter - increase the length by 1 by pre-padding a zero coefficient
|
||||||
|
float[] extendedTaps = new float[taps.length + 1];
|
||||||
|
System.arraycopy(taps, 0, extendedTaps, 1, taps.length);
|
||||||
|
|
||||||
if(logResults)
|
if(logResults)
|
||||||
{
|
{
|
||||||
mLog.debug("Polyphase Channelizer Filter Design Summary");
|
mLog.debug("Polyphase Channelizer Filter Design Summary");
|
||||||
@@ -839,11 +843,11 @@ public class FilterFactory
|
|||||||
mLog.debug("Channel Bandwidth: " + channelBandwidth);
|
mLog.debug("Channel Bandwidth: " + channelBandwidth);
|
||||||
mLog.debug("Channels: " + channels);
|
mLog.debug("Channels: " + channels);
|
||||||
mLog.debug("Window Type: " + windowType.name());
|
mLog.debug("Window Type: " + windowType.name());
|
||||||
mLog.debug("Taps Per Channel: " + tapsPerChannel + " (Requested:" + tapsPerChannel + ")");
|
mLog.debug("Taps Per Channel - Requested:" + tapsPerChannel + " Actual:" + ((double)extendedTaps.length / (double)channels));
|
||||||
mLog.debug("Filter Length: " + (taps.length + 1));
|
mLog.debug("Filter Length: " + (extendedTaps.length));
|
||||||
mLog.debug("Requested Cutoff Frequency: " + (sampleRate * bandEdge));
|
mLog.debug("Requested Cutoff Frequency: " + (sampleRate * bandEdge));
|
||||||
mLog.debug("Actual Cutoff Frequency: " + (sampleRate * cutoffFrequency));
|
mLog.debug("Actual Cutoff Frequency: " + (sampleRate * cutoffFrequency));
|
||||||
mLog.debug("Attenuation at 1.00 OBJECTIVE: " + PERFECT_RECONSTRUCTION_GAIN_AT_BAND_EDGE);
|
mLog.debug("Attenuation at 1.0 OBJECTIVE: " + PERFECT_RECONSTRUCTION_GAIN_AT_BAND_EDGE);
|
||||||
mLog.debug("Attenuation at 1.00 Channels: " + evaluate(taps, bandEdge * 1.00));
|
mLog.debug("Attenuation at 1.00 Channels: " + evaluate(taps, bandEdge * 1.00));
|
||||||
mLog.debug("Attenuation at 1.25 Channels: " + evaluate(taps, bandEdge * 1.25));
|
mLog.debug("Attenuation at 1.25 Channels: " + evaluate(taps, bandEdge * 1.25));
|
||||||
mLog.debug("Attenuation at 1.50 Channels: " + evaluate(taps, bandEdge * 1.50));
|
mLog.debug("Attenuation at 1.50 Channels: " + evaluate(taps, bandEdge * 1.50));
|
||||||
@@ -851,10 +855,6 @@ public class FilterFactory
|
|||||||
mLog.debug("Attenuation at 2.00 Channels: " + evaluate(taps, bandEdge * 2.00));
|
mLog.debug("Attenuation at 2.00 Channels: " + evaluate(taps, bandEdge * 2.00));
|
||||||
}
|
}
|
||||||
|
|
||||||
//This is an odd length filter - increase the length by 1 by pre-padding a zero coefficient
|
|
||||||
float[] extendedTaps = new float[taps.length + 1];
|
|
||||||
System.arraycopy(taps, 0, extendedTaps, 1, taps.length);
|
|
||||||
|
|
||||||
return extendedTaps;
|
return extendedTaps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,18 +36,39 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener<Co
|
|||||||
|
|
||||||
private Broadcaster<SourceEvent> mSourceChangeBroadcaster = new Broadcaster();
|
private Broadcaster<SourceEvent> mSourceChangeBroadcaster = new Broadcaster();
|
||||||
private List<PolyphaseChannelSource> mChannels = new CopyOnWriteArrayList<>();
|
private List<PolyphaseChannelSource> mChannels = new CopyOnWriteArrayList<>();
|
||||||
|
private int mSampleRate;
|
||||||
private int mChannelCount;
|
private int mChannelCount;
|
||||||
private int mChannelSampleRate;
|
private double mChannelSampleRate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Complex sample polyphase channelizer
|
* Complex sample polyphase channelizer
|
||||||
*
|
*
|
||||||
* @param channelCount
|
* @param channelCount
|
||||||
*/
|
*/
|
||||||
public AbstractComplexPolyphaseChannelizer(int channelCount, int channelSampleRate)
|
public AbstractComplexPolyphaseChannelizer(int sampleRate, int channelCount)
|
||||||
{
|
{
|
||||||
mChannelCount = channelCount;
|
mChannelCount = channelCount;
|
||||||
mChannelSampleRate = channelSampleRate;
|
mSampleRate = sampleRate;
|
||||||
|
mChannelSampleRate = (double)mSampleRate / (double)mChannelCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input sample rate for this channelizer
|
||||||
|
* @return sample rate in hertz
|
||||||
|
*/
|
||||||
|
public int getSampleRate()
|
||||||
|
{
|
||||||
|
return mSampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the input sample rate for for this channelizer
|
||||||
|
* @param sampleRate in hertz
|
||||||
|
*/
|
||||||
|
public void setSampleRate(int sampleRate)
|
||||||
|
{
|
||||||
|
mSampleRate = sampleRate;
|
||||||
|
mChannelSampleRate = (double)mSampleRate / (double)mChannelCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,33 +80,14 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener<Co
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Output sample rate for each channel
|
* Output channel sample rate
|
||||||
* @return sample rate in hertz
|
* @return sample rate in hertz
|
||||||
*/
|
*/
|
||||||
public int getChannelSampleRate()
|
public double getChannelSampleRate()
|
||||||
{
|
{
|
||||||
return mChannelSampleRate;
|
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.
|
* Dispatches the processed channel samples to any registered polyphase channel outputs.
|
||||||
*
|
*
|
||||||
|
|||||||
588
src/dsp/filter/channelizer/ChannelCalculator.java
Normal file
588
src/dsp/filter/channelizer/ChannelCalculator.java
Normal file
@@ -0,0 +1,588 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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 source.tuner.TunerChannel;
|
||||||
|
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ChannelCalculator
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(ChannelCalculator.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Policy to determine desired corrective action for methods that calculate an index for a frequency or a frequency
|
||||||
|
* from an index where the frequency may exist either at the overlapping boundary of two indexes, or in the middle
|
||||||
|
* of the wrap-around (N/2) index.
|
||||||
|
*/
|
||||||
|
private enum IndexBoundaryPolicy
|
||||||
|
{
|
||||||
|
ADJUST_POSITIVE,
|
||||||
|
ADJUST_NEGATIVE
|
||||||
|
}
|
||||||
|
|
||||||
|
private int mSampleRate;
|
||||||
|
private int mChannelCount;
|
||||||
|
private double mCenterFrequency;
|
||||||
|
private double mChannelBandwidth;
|
||||||
|
private double mOversampling = 1.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates channel index(es) from a polyphase channelizer output to source a DDC polyphase channel source
|
||||||
|
* with a specified channel center frequency and bandwidth.
|
||||||
|
*
|
||||||
|
* Polyphase channel indexes are not linear/consecutive owing to the use of the FFT to rotate channels to their
|
||||||
|
* center frequencies following filtering in the polyphase filter bank. As such, this calculator provides methods
|
||||||
|
* to determine the min/center/max frequency of each channel that is adjusted for the current center frequency
|
||||||
|
* value and to determine the channel index(es) that need to be targeted to produce a requested output channel
|
||||||
|
* with a specific frequency and bandwdith.
|
||||||
|
*
|
||||||
|
* @param sampleRate in hertz for the input sample rate from the sample source (ie tuner)
|
||||||
|
* @param channelCount number of channels in the polyphase filter bank
|
||||||
|
* @param centerFrequency center frequency for the input sample stream
|
||||||
|
* @param oversampling set to 1.0 for no oversample, or greater than 1.0 for oversampling
|
||||||
|
*/
|
||||||
|
public ChannelCalculator(int sampleRate, int channelCount, double centerFrequency, double oversampling)
|
||||||
|
{
|
||||||
|
mSampleRate = sampleRate;
|
||||||
|
mChannelCount = channelCount;
|
||||||
|
mCenterFrequency = centerFrequency;
|
||||||
|
mOversampling = oversampling;
|
||||||
|
|
||||||
|
updateChannelBandwidth();;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the channel bandwidth from the sample rate and channel count.
|
||||||
|
*/
|
||||||
|
private void updateChannelBandwidth()
|
||||||
|
{
|
||||||
|
mChannelBandwidth = (double)mSampleRate / (double)mChannelCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Center frequency for the channelizer
|
||||||
|
*/
|
||||||
|
public double getCenterFrequency()
|
||||||
|
{
|
||||||
|
return mCenterFrequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the center frequency for the channelizer
|
||||||
|
*/
|
||||||
|
public void setCenterFrequency(double frequency)
|
||||||
|
{
|
||||||
|
mCenterFrequency = frequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of channels being channelized by the polyphase channelizer
|
||||||
|
*/
|
||||||
|
public int getChannelCount()
|
||||||
|
{
|
||||||
|
return mChannelCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the number of channels being channelized by the polyphase channelizer
|
||||||
|
*/
|
||||||
|
public void setChannelCount(int channelCount)
|
||||||
|
{
|
||||||
|
mChannelCount = channelCount;
|
||||||
|
updateChannelBandwidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies the index of the wrap-around channel meaning that the lower half of the channel exists at the upper
|
||||||
|
* end of the spectrum and the upper half of the channel exists at the lower end of the spectrum
|
||||||
|
*
|
||||||
|
* @return wrap-around channel index
|
||||||
|
*/
|
||||||
|
public int getWrapAroundIndex()
|
||||||
|
{
|
||||||
|
return mChannelCount / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PolyphaseChannelManager input sample rate
|
||||||
|
* @return input sample rate in hertz
|
||||||
|
*/
|
||||||
|
public int getSampleRate()
|
||||||
|
{
|
||||||
|
return mSampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the channel count using the sample rate argument to calculate the number of channels from the
|
||||||
|
* already established channel bandwidth.
|
||||||
|
*
|
||||||
|
* @param sampleRate to use for channel count
|
||||||
|
*/
|
||||||
|
public void setSampleRate(int sampleRate)
|
||||||
|
{
|
||||||
|
setChannelCount((int)((double)sampleRate / getChannelBandwidth()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Channel bandwidth. Note: this value may be smaller than the channel sample rate when oversampling
|
||||||
|
* is being used.
|
||||||
|
*/
|
||||||
|
public double getChannelBandwidth()
|
||||||
|
{
|
||||||
|
return mChannelBandwidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Half of the channel bandwidth.
|
||||||
|
*/
|
||||||
|
public double getHalfChannelBandwidth()
|
||||||
|
{
|
||||||
|
return mChannelBandwidth / 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Oversampling rate produced by the channelizer.
|
||||||
|
*
|
||||||
|
* @return 1.0 for no oversampling, or a value greater than 1.0 when oversampling is applied.
|
||||||
|
*/
|
||||||
|
public double getOversampling()
|
||||||
|
{
|
||||||
|
return mOversampling;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the oversampling rate being applied by the polyphase channelizer
|
||||||
|
* @param oversampling rate where 1.0 indicates no oversampling, and a value greater than 1.0 indicates
|
||||||
|
* that the polyphase channelizer is oversampling each channel.
|
||||||
|
*/
|
||||||
|
public void setOversampling(double oversampling)
|
||||||
|
{
|
||||||
|
mOversampling = oversampling;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Channel sample rate. This value is the product of the filtered channel bandwidth times the
|
||||||
|
* oversampling rate.
|
||||||
|
*/
|
||||||
|
public double getChannelSampleRate()
|
||||||
|
{
|
||||||
|
return (double)getChannelBandwidth() * getOversampling();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PolyphaseChannelManager input minimum frequency
|
||||||
|
* @return minimum frequency in hertz
|
||||||
|
*/
|
||||||
|
public long getMinimumFrequency()
|
||||||
|
{
|
||||||
|
return (long)(getCenterFrequency() - (getSampleRate() / 2.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PolyphaseChannelManager input maximum frequency
|
||||||
|
* @return maximum frequency in hertz
|
||||||
|
*/
|
||||||
|
public long getMaximumFrequency()
|
||||||
|
{
|
||||||
|
return (long)(getCenterFrequency() + (getSampleRate() / 2.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies the channelizer channel index(es) required to produce a tuner channel with the specified center
|
||||||
|
* frequency and channel bandwidth.
|
||||||
|
*
|
||||||
|
* @param tunerChannel specifying the desired channel frequency and bandwidth
|
||||||
|
*
|
||||||
|
* @return an array of one or more polyphase channelizer index(es) to target in order to produce an output channel
|
||||||
|
* for the desired frequency and bandwidth.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException if the tuner channel's minimum or maximum frequency is not contained
|
||||||
|
* within the channelizer's minimum or maximum frequency range
|
||||||
|
*/
|
||||||
|
public List<Integer> getChannelIndexes(TunerChannel tunerChannel) throws IllegalArgumentException
|
||||||
|
{
|
||||||
|
if(tunerChannel.getMinFrequency() < getMinimumFrequency() ||
|
||||||
|
tunerChannel.getMaxFrequency() > getMaximumFrequency())
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Requested channel cannot be provided by this " +
|
||||||
|
"channelizer. Requested channel [" + tunerChannel.getMinFrequency() + " - " +
|
||||||
|
tunerChannel.getMaxFrequency() + "] exceeds current channelizer frequency range [" +
|
||||||
|
getMinimumFrequency() + " - " + getMaximumFrequency() + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
int minIndex = getIndexForFrequency(tunerChannel.getMinFrequency(), IndexBoundaryPolicy.ADJUST_POSITIVE);
|
||||||
|
int maxIndex = getIndexForFrequency(tunerChannel.getMaxFrequency(), IndexBoundaryPolicy.ADJUST_NEGATIVE);
|
||||||
|
|
||||||
|
//Detect the unsourceable situation where the wrap around index would be included in both the minimum index
|
||||||
|
//and the maximum index, meaning that the tuner channel bandwidth is nearly the same as the input sample rate.
|
||||||
|
if(minIndex == getWrapAroundIndex() && maxIndex == getWrapAroundIndex())
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Requested tuner channel cannot be provided. Requested bandwidth is " +
|
||||||
|
"within two channel bandwidths of the sample rate.");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Integer> indexes = new ArrayList<>();
|
||||||
|
|
||||||
|
indexes.add(minIndex);
|
||||||
|
|
||||||
|
if(minIndex != maxIndex)
|
||||||
|
{
|
||||||
|
//Ensure our min/max indexes are within the correct bounds 0 <> N-1 to safeguard the while loop below
|
||||||
|
if(minIndex < 0 || minIndex >= getChannelCount() || maxIndex < 0 || maxIndex >= getChannelCount())
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Something went wrong while calculating the min and max polyphase " +
|
||||||
|
"channel indexes for tuner channel: " + tunerChannel);
|
||||||
|
}
|
||||||
|
|
||||||
|
int pointer = minIndex + 1;
|
||||||
|
|
||||||
|
if(pointer >= getChannelCount())
|
||||||
|
{
|
||||||
|
pointer -= getChannelCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
while(pointer != maxIndex)
|
||||||
|
{
|
||||||
|
indexes.add(pointer);
|
||||||
|
|
||||||
|
pointer++;
|
||||||
|
|
||||||
|
if(pointer >= getChannelCount())
|
||||||
|
{
|
||||||
|
pointer -= getChannelCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
indexes.add(maxIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return indexes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the index for the specified frequency. Certain frequencies can exist as both the maximum frequency
|
||||||
|
* of one channel and the minimum frequency of an adjacent channel. The index boundary policy identifies your
|
||||||
|
* choice to select the higher frequency channel index (ADJUST_POSITIVE) or the lower frequency channel
|
||||||
|
* index (ADJUST_NEGATIVE).
|
||||||
|
*
|
||||||
|
* @param frequency to determine channel index for
|
||||||
|
* @param indexBoundaryPolicy to determine the behavior when the frequency overlaps two channel boundaries
|
||||||
|
* @return index for the frequency
|
||||||
|
*/
|
||||||
|
public int getIndexForFrequency(long frequency, IndexBoundaryPolicy indexBoundaryPolicy)
|
||||||
|
{
|
||||||
|
double offset = frequency - getCenterFrequency();
|
||||||
|
|
||||||
|
if(Math.abs(offset) < getHalfChannelBandwidth())
|
||||||
|
{
|
||||||
|
return 0; //Center index
|
||||||
|
}
|
||||||
|
|
||||||
|
if(offset > 0)
|
||||||
|
{
|
||||||
|
offset += getHalfChannelBandwidth();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
offset -= getHalfChannelBandwidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
int indexOffset = (int)(offset / getChannelBandwidth());
|
||||||
|
|
||||||
|
if(indexOffset < 0)
|
||||||
|
{
|
||||||
|
indexOffset += getChannelCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(indexBoundaryPolicy == IndexBoundaryPolicy.ADJUST_POSITIVE &&
|
||||||
|
isOverlapFrequency(frequency, indexOffset, indexOffset + 1))
|
||||||
|
{
|
||||||
|
indexOffset = normalize(indexOffset + 1);
|
||||||
|
}
|
||||||
|
else if(indexBoundaryPolicy == IndexBoundaryPolicy.ADJUST_NEGATIVE &&
|
||||||
|
isOverlapFrequency(frequency, indexOffset - 1, indexOffset))
|
||||||
|
{
|
||||||
|
indexOffset = normalize(indexOffset - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return indexOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the frequency is an overlapping frequency between channel index 1 and channel index 2 where the
|
||||||
|
* maximum frequency of index 1 is the same as the minimum frequency of index 2 and both of these frequencies are
|
||||||
|
* the same as the frequency argument.
|
||||||
|
*
|
||||||
|
* Note: both index 1 and index 2 will be normalized to the range of 0 <> N-1 prior to determining overlap
|
||||||
|
*
|
||||||
|
* @param frequency to test for overlap between index 1 and 2
|
||||||
|
* @param index1 which is the lower channel index located next to index 2.
|
||||||
|
* @param index2 which is the higher channel index located next to index 1.
|
||||||
|
* @return true if frequency = index1 maximum frequency = index2 minimum frequency
|
||||||
|
*/
|
||||||
|
public boolean isOverlapFrequency(long frequency, int index1, int index2)
|
||||||
|
{
|
||||||
|
index1 = normalize(index1);
|
||||||
|
index2 = normalize(index2);
|
||||||
|
|
||||||
|
//Check that these two indexes are side by side
|
||||||
|
int delta = normalize(index2 - index1);
|
||||||
|
|
||||||
|
if(delta != 1)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
long index1MaximumFrequency = (long)getIndexMaximumFrequency(index1, IndexBoundaryPolicy.ADJUST_POSITIVE);
|
||||||
|
long index2MinimumFrequency = (long)getIndexMinimumFrequency(index2, IndexBoundaryPolicy.ADJUST_NEGATIVE);
|
||||||
|
|
||||||
|
if(index1 == getWrapAroundIndex())
|
||||||
|
{
|
||||||
|
index1MaximumFrequency = (long)getIndexMaximumFrequency(index1, IndexBoundaryPolicy.ADJUST_NEGATIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(index2 == getWrapAroundIndex())
|
||||||
|
{
|
||||||
|
index2MinimumFrequency = (long)getIndexMinimumFrequency(index2, IndexBoundaryPolicy.ADJUST_POSITIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return frequency == index1MaximumFrequency && frequency == index2MinimumFrequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes the index to the range of 0 <> N-1 channel indexes</>
|
||||||
|
*/
|
||||||
|
private int normalize(int index)
|
||||||
|
{
|
||||||
|
while(index < 0)
|
||||||
|
{
|
||||||
|
index += getChannelCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
while(index >= getChannelCount())
|
||||||
|
{
|
||||||
|
index -= getChannelCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the center frequency for the channel identified by the index in the channel results array.
|
||||||
|
*
|
||||||
|
* @param index to determine the channel center frequency
|
||||||
|
* @return center frequency for the indexed channel
|
||||||
|
*/
|
||||||
|
public double getIndexCenterFrequency(int index, IndexBoundaryPolicy indexBoundaryPolicy)
|
||||||
|
{
|
||||||
|
if(index < 0 || index >= getChannelCount())
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Illegal channel index");
|
||||||
|
}
|
||||||
|
|
||||||
|
int wrapAroundIndex = getWrapAroundIndex();
|
||||||
|
|
||||||
|
if(index == wrapAroundIndex)
|
||||||
|
{
|
||||||
|
if(indexBoundaryPolicy == IndexBoundaryPolicy.ADJUST_POSITIVE)
|
||||||
|
{
|
||||||
|
return getCenterFrequency() + (index * getChannelBandwidth());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return getCenterFrequency() - (index * getChannelBandwidth());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(index < wrapAroundIndex)
|
||||||
|
{
|
||||||
|
return getCenterFrequency() + (index * getChannelBandwidth());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return getCenterFrequency() - ((getChannelCount() - index) * getChannelBandwidth());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the minimum frequency band edge for the channel identified by the index in the channel results
|
||||||
|
* array.
|
||||||
|
* @param index to calculate the minimum band edge frequency for the channel.
|
||||||
|
* @param indexBoundaryPolicy for the wrap around index to indicate if this method should return the minimum
|
||||||
|
* frequency of the upper spectrum half of the wrap around channel (ADJUST_POSITIVE) or the minimum frequency of the
|
||||||
|
* lower spectrum half of the wrap around channel (ADJUST_NEGATIVE)
|
||||||
|
* @return frequency of the minimum band edge
|
||||||
|
*/
|
||||||
|
public double getIndexMinimumFrequency(int index, IndexBoundaryPolicy indexBoundaryPolicy)
|
||||||
|
{
|
||||||
|
if(index < 0 || index >= getChannelCount())
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Can't determine minimum frequency - illegal channel index [" +
|
||||||
|
index + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
int wrapAroundIndex = getWrapAroundIndex();
|
||||||
|
|
||||||
|
if(index == wrapAroundIndex)
|
||||||
|
{
|
||||||
|
if(indexBoundaryPolicy == IndexBoundaryPolicy.ADJUST_POSITIVE)
|
||||||
|
{
|
||||||
|
return getCenterFrequency() + ((double)index * getChannelBandwidth()) - getHalfChannelBandwidth();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return getIndexCenterFrequency(index, indexBoundaryPolicy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(index <= wrapAroundIndex)
|
||||||
|
{
|
||||||
|
return getCenterFrequency() + ((double)index * getChannelBandwidth()) - getHalfChannelBandwidth();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return getCenterFrequency() - ((double)(getChannelCount() - index) * getChannelBandwidth()) -
|
||||||
|
getHalfChannelBandwidth();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the maximum frequency band edge for the channel identified by the index in the channel results
|
||||||
|
* array.
|
||||||
|
* @param index to calculate the maximum band edge frequency for the channel.
|
||||||
|
* @param indexBoundaryPolicy for the wrap around index to indicate if this method should return the maximum
|
||||||
|
* frequency of the upper spectrum half of the wrap around channel (ADJUST_POSITIVE) or the maximum frequency of the
|
||||||
|
* lower spectrum half of the wrap around channel (ADJUST_NEGATIVE)
|
||||||
|
* @return frequency of the maximum band edge
|
||||||
|
*/
|
||||||
|
public double getIndexMaximumFrequency(int index, IndexBoundaryPolicy indexBoundaryPolicy)
|
||||||
|
{
|
||||||
|
if(index < 0 || index >= getChannelCount())
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Can't determine maximum frequency - illegal channel index [" +
|
||||||
|
index + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
int wrapAroundIndex = getWrapAroundIndex();
|
||||||
|
|
||||||
|
if(index == wrapAroundIndex)
|
||||||
|
{
|
||||||
|
if(indexBoundaryPolicy == IndexBoundaryPolicy.ADJUST_POSITIVE)
|
||||||
|
{
|
||||||
|
return getIndexCenterFrequency(index, indexBoundaryPolicy);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return getCenterFrequency() - ((double)index * getChannelBandwidth() - getHalfChannelBandwidth());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(index <= wrapAroundIndex)
|
||||||
|
{
|
||||||
|
return getCenterFrequency() + ((double)index * getChannelBandwidth()) + getHalfChannelBandwidth();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return getCenterFrequency() - ((double)(getChannelCount() - index) * getChannelBandwidth()) +
|
||||||
|
getHalfChannelBandwidth();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the center frequency for a channel that will be synthesized from the channel indexes.
|
||||||
|
* @param indexes that are contiguous in the spectrum
|
||||||
|
* @return center frequency in hertz
|
||||||
|
*/
|
||||||
|
public long getCenterFrequencyForIndexes(List<Integer> indexes)
|
||||||
|
{
|
||||||
|
if(indexes.isEmpty())
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Indexes cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
int middleIndex = indexes.get(indexes.size() / 2);
|
||||||
|
|
||||||
|
if(indexes.size() % 2 == 0)
|
||||||
|
{
|
||||||
|
//Even number of indexes
|
||||||
|
return (long)getIndexMinimumFrequency(middleIndex, IndexBoundaryPolicy.ADJUST_POSITIVE);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Odd number of indexes
|
||||||
|
return (long)getIndexCenterFrequency(middleIndex, IndexBoundaryPolicy.ADJUST_POSITIVE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String representation of the channel calculator settings
|
||||||
|
*/
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("Channels:").append(getChannelCount());
|
||||||
|
sb.append(" Sample Rate:").append(getSampleRate());
|
||||||
|
sb.append(" Channel Bandwidth:").append(getChannelBandwidth());
|
||||||
|
sb.append(" Channel Rate:").append(getChannelSampleRate());
|
||||||
|
sb.append(" Min:").append(getMinimumFrequency());
|
||||||
|
sb.append(" Max:").append(getMaximumFrequency());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
mLog.debug("Starting ....");
|
||||||
|
|
||||||
|
int channelBandwidth = 12500;
|
||||||
|
int sampleRate = 100000; //Airspy
|
||||||
|
int channelCount = sampleRate / channelBandwidth;
|
||||||
|
double centerFrequency = 455000000;
|
||||||
|
|
||||||
|
ChannelCalculator calculator = new ChannelCalculator(sampleRate, channelCount, centerFrequency, 2.0);
|
||||||
|
|
||||||
|
mLog.debug(calculator.toString());
|
||||||
|
|
||||||
|
DecimalFormat decimalFormat = new DecimalFormat("000,000,000.0");
|
||||||
|
|
||||||
|
int wrapAroundIndex = calculator.getWrapAroundIndex();
|
||||||
|
|
||||||
|
IndexBoundaryPolicy indexBoundaryPolicy = IndexBoundaryPolicy.ADJUST_NEGATIVE;
|
||||||
|
|
||||||
|
for(int index = wrapAroundIndex; index < calculator.getChannelCount(); index++)
|
||||||
|
{
|
||||||
|
mLog.debug("Index:" + index + " Min:" + decimalFormat.format(calculator.getIndexMinimumFrequency(index, indexBoundaryPolicy)) +
|
||||||
|
" Center:" + decimalFormat.format(calculator.getIndexCenterFrequency(index, indexBoundaryPolicy)) +
|
||||||
|
" Max:" + decimalFormat.format(calculator.getIndexMaximumFrequency(index, indexBoundaryPolicy)));
|
||||||
|
}
|
||||||
|
|
||||||
|
indexBoundaryPolicy = IndexBoundaryPolicy.ADJUST_POSITIVE;
|
||||||
|
|
||||||
|
for(int index = 0; index <= wrapAroundIndex; index++)
|
||||||
|
{
|
||||||
|
mLog.debug("Index:" + index + " Min:" + decimalFormat.format(calculator.getIndexMinimumFrequency(index, indexBoundaryPolicy)) +
|
||||||
|
" Center:" + decimalFormat.format(calculator.getIndexCenterFrequency(index, indexBoundaryPolicy)) +
|
||||||
|
" Max:" + decimalFormat.format(calculator.getIndexMaximumFrequency(index, indexBoundaryPolicy)));
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.debug("Finished");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,323 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* 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 source.tuner.TunerChannel;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class ChannelIndexCalculator
|
|
||||||
{
|
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelIndexCalculator.class);
|
|
||||||
|
|
||||||
private long mCenterFrequency;
|
|
||||||
private int mChannelCount;
|
|
||||||
private int mChannelBandwidth;
|
|
||||||
private double mOversampling = 1.0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the channel index(es) from a polyphase channelizer output to source a DDC polyphase channel source
|
|
||||||
* with a specified channel center frequency and bandwidth.
|
|
||||||
*
|
|
||||||
* @param centerFrequency
|
|
||||||
* @param channelCount
|
|
||||||
* @param channelBandwidth
|
|
||||||
* @param oversampling set to 1.0 for no oversample, or greater than 1.0 for oversampling
|
|
||||||
*/
|
|
||||||
public ChannelIndexCalculator(long centerFrequency, int channelCount, int channelBandwidth, double oversampling)
|
|
||||||
{
|
|
||||||
mCenterFrequency = centerFrequency;
|
|
||||||
mChannelCount = channelCount;
|
|
||||||
mChannelBandwidth = channelBandwidth;
|
|
||||||
mOversampling = oversampling;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Center frequency for the channelizer
|
|
||||||
*/
|
|
||||||
public long getCenterFrequency()
|
|
||||||
{
|
|
||||||
return mCenterFrequency;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets or changes the center frequency for the channelizer
|
|
||||||
*/
|
|
||||||
public void setCenterFrequency(long frequency)
|
|
||||||
{
|
|
||||||
mCenterFrequency = frequency;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Number of channels being channelized by the polyphase channelizer
|
|
||||||
*/
|
|
||||||
public int getChannelCount()
|
|
||||||
{
|
|
||||||
return mChannelCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the number of channels being channelized by the polyphase channelizer
|
|
||||||
*/
|
|
||||||
public void setChannelCount(int channelCount)
|
|
||||||
{
|
|
||||||
mChannelCount = channelCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the channel count using the sample rate argument to calculate the number of channels from the
|
|
||||||
* already established channel bandwidth.
|
|
||||||
*
|
|
||||||
* @param sampleRate to use for channel count
|
|
||||||
*/
|
|
||||||
public void setSampleRate(int sampleRate)
|
|
||||||
{
|
|
||||||
setChannelCount(sampleRate / getChannelBandwidth());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Channel bandwidth. Note: this value may be smaller than the channel sample rate when oversampling
|
|
||||||
* is being used.
|
|
||||||
*/
|
|
||||||
public int getChannelBandwidth()
|
|
||||||
{
|
|
||||||
return mChannelBandwidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Half of the channel bandwidth.
|
|
||||||
*/
|
|
||||||
public int getHalfChannelBandwidth()
|
|
||||||
{
|
|
||||||
return mChannelBandwidth / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the channel bandwidth in hertz.
|
|
||||||
*/
|
|
||||||
public void setChannelBandwidth(int channelBandwidth)
|
|
||||||
{
|
|
||||||
mChannelBandwidth = channelBandwidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Oversampling rate produced by the channelizer.
|
|
||||||
*
|
|
||||||
* @return 1.0 for no oversampling, or a value greater than 1.0 when oversampling is applied.
|
|
||||||
*/
|
|
||||||
public double getOversampling()
|
|
||||||
{
|
|
||||||
return mOversampling;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the oversampling rate being applied by the channelizer
|
|
||||||
* @param oversampling rate where 1.0 indicates no oversampling, and a value greater than 1.0 indicates
|
|
||||||
* that the polyphase channelizer is oversampling each channel.
|
|
||||||
*/
|
|
||||||
public void setOversampling(double oversampling)
|
|
||||||
{
|
|
||||||
mOversampling = oversampling;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Channel sample rate. This value is the product of the filtered channel bandwidth times the
|
|
||||||
* oversampling rate.
|
|
||||||
*/
|
|
||||||
public double getChannelSampleRate()
|
|
||||||
{
|
|
||||||
return (double)getChannelBandwidth() * getOversampling();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PolyphaseChannelManager input sample rate
|
|
||||||
* @return input sample rate in hertz
|
|
||||||
*/
|
|
||||||
public int getSampleRate()
|
|
||||||
{
|
|
||||||
return getChannelBandwidth() * getChannelCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PolyphaseChannelManager input minimum frequency
|
|
||||||
* @return minimum frequency in hertz
|
|
||||||
*/
|
|
||||||
public long getMinimumFrequency()
|
|
||||||
{
|
|
||||||
return getCenterFrequency() - (getSampleRate() / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PolyphaseChannelManager input maximum frequency
|
|
||||||
* @return maximum frequency in hertz
|
|
||||||
*/
|
|
||||||
public long getMaximumFrequency()
|
|
||||||
{
|
|
||||||
return getCenterFrequency() + (getSampleRate() / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Identifies the channelizer output channel(s) required to produce a tuner channel with the specified center
|
|
||||||
* frequency and channel bandwidth.
|
|
||||||
*
|
|
||||||
* @param tunerChannel specifying the desired channel frequency and bandwidth
|
|
||||||
* @return an array of one or more polyphase channelizer channels to target in order to produce an output channel
|
|
||||||
* for the desired frequency and bandwidth.
|
|
||||||
* @throws IllegalArgumentException if the tuner channel's minimum or maximum frequency is not contained
|
|
||||||
* within the channelizer's minimum or maximum frequency range
|
|
||||||
*/
|
|
||||||
public List<Integer> getPolyphaseChannelIndexes(TunerChannel tunerChannel) throws IllegalArgumentException
|
|
||||||
{
|
|
||||||
long min = getMinimumFrequency();
|
|
||||||
long max = getMaximumFrequency();
|
|
||||||
|
|
||||||
if(tunerChannel.getMinFrequency() < getMinimumFrequency() ||
|
|
||||||
tunerChannel.getMaxFrequency() > getMaximumFrequency())
|
|
||||||
{
|
|
||||||
throw new IllegalArgumentException("Requested channel cannot be provided by this " +
|
|
||||||
"channelizer. Requested channel [" + tunerChannel.getMinFrequency() + " - " +
|
|
||||||
tunerChannel.getMaxFrequency() + "] exceeds current channelizer frequency range [" +
|
|
||||||
getMinimumFrequency() + " - " + getMaximumFrequency() + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Integer> indices = new ArrayList<>();
|
|
||||||
|
|
||||||
long startFrequencyOffset = (tunerChannel.getMinFrequency() - getMinimumFrequency());
|
|
||||||
int startIndex = (int)(startFrequencyOffset / getChannelBandwidth());
|
|
||||||
|
|
||||||
long endFrequencyOffset = (tunerChannel.getMaxFrequency() - getMinimumFrequency());
|
|
||||||
int endIndex = (int)(endFrequencyOffset / getChannelBandwidth());
|
|
||||||
|
|
||||||
int currentIndex = startIndex;
|
|
||||||
|
|
||||||
while(currentIndex <= endIndex)
|
|
||||||
{
|
|
||||||
long currentIndexMin = getMinimumFrequencyForChannelIndex(currentIndex);
|
|
||||||
long currentIndexMax = getMaximumFrequencyForChannelIndex(currentIndex);
|
|
||||||
|
|
||||||
//Case 1: tuner channel completely aligns with indexed channel
|
|
||||||
if(currentIndexMin <= tunerChannel.getMinFrequency() && tunerChannel.getMaxFrequency() <= currentIndexMax)
|
|
||||||
{
|
|
||||||
indices.add(currentIndex);
|
|
||||||
}
|
|
||||||
//Case 2: tuner channel min frequency aligns and max frequency exceeds
|
|
||||||
else if(currentIndexMin <= tunerChannel.getMinFrequency() &&
|
|
||||||
tunerChannel.getMinFrequency() < currentIndexMax &&
|
|
||||||
tunerChannel.getMaxFrequency() > currentIndexMax)
|
|
||||||
{
|
|
||||||
indices.add(currentIndex);
|
|
||||||
}
|
|
||||||
//Case 3: tuner channel min frequency exceeds and max frequency aligns
|
|
||||||
else if(tunerChannel.getMinFrequency() < currentIndexMin &&
|
|
||||||
tunerChannel.getMaxFrequency() > currentIndexMin &&
|
|
||||||
tunerChannel.getMaxFrequency() <= currentIndexMax)
|
|
||||||
{
|
|
||||||
indices.add(currentIndex);
|
|
||||||
}
|
|
||||||
//Case 4: tuner channel encapsulates the indexed channel
|
|
||||||
else if(tunerChannel.getMinFrequency() < currentIndexMin && tunerChannel.getMaxFrequency() > currentIndexMax)
|
|
||||||
{
|
|
||||||
indices.add(currentIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return indices;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the center frequency for the channel identified by the index in the channel results array.
|
|
||||||
*
|
|
||||||
* @param index to determine the channel center frequency
|
|
||||||
* @return center frequency for the indexed channel
|
|
||||||
*/
|
|
||||||
public long getCenterFrequencyForChannelIndex(int index)
|
|
||||||
{
|
|
||||||
if(index < 0 || index >= getChannelCount())
|
|
||||||
{
|
|
||||||
throw new IllegalArgumentException("Illegal channel index");
|
|
||||||
}
|
|
||||||
|
|
||||||
return getMinimumFrequency() + getHalfChannelBandwidth() + (index * getChannelBandwidth());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the minimum frequency band edge for the channel identified by the index in the channel results
|
|
||||||
* array.
|
|
||||||
* @param index to calculate the minimum band edge frequency for the channel.
|
|
||||||
* @return frequency of the minimum band edge
|
|
||||||
*/
|
|
||||||
public long getMinimumFrequencyForChannelIndex(int index)
|
|
||||||
{
|
|
||||||
if(index < 0 || index > getChannelCount())
|
|
||||||
{
|
|
||||||
throw new IllegalArgumentException("Can't determine minimum frequency - illegal channel index [" +
|
|
||||||
index + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
return getMinimumFrequency() + (index * getChannelBandwidth());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the maximum frequency band edge for the channel identified by the index in the channel results
|
|
||||||
* array.
|
|
||||||
* @param index to calculate the maximum band edge frequency for the channel.
|
|
||||||
* @return frequency of the maximum band edge
|
|
||||||
*/
|
|
||||||
public long getMaximumFrequencyForChannelIndex(int index)
|
|
||||||
{
|
|
||||||
if(index < 0 || index > getChannelCount())
|
|
||||||
{
|
|
||||||
throw new IllegalArgumentException("Can't determine maximum frequency - illegal channel index [" +
|
|
||||||
index + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
//Maximum frequency is the same as the minimum frequency of the next higher indexed channel
|
|
||||||
return getMinimumFrequency() + ((index + 1) * getChannelBandwidth());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args)
|
|
||||||
{
|
|
||||||
mLog.debug("Starting ....");
|
|
||||||
|
|
||||||
int channelCount = 10;
|
|
||||||
|
|
||||||
ChannelIndexCalculator channelIndexCalculator =
|
|
||||||
new ChannelIndexCalculator(100062500, channelCount, 12500, 2.0);
|
|
||||||
|
|
||||||
for(int x = 0; x < channelCount; x++)
|
|
||||||
{
|
|
||||||
mLog.debug("Channel " + x + ": " + channelIndexCalculator.getMinimumFrequencyForChannelIndex(x) + " - " +
|
|
||||||
channelIndexCalculator.getMaximumFrequencyForChannelIndex(x) +
|
|
||||||
" Center: " + channelIndexCalculator.getCenterFrequencyForChannelIndex(x));
|
|
||||||
}
|
|
||||||
|
|
||||||
TunerChannel tunerChannel = new TunerChannel(100006250, 12500);
|
|
||||||
|
|
||||||
List<Integer> indices = channelIndexCalculator.getPolyphaseChannelIndexes(tunerChannel);
|
|
||||||
|
|
||||||
mLog.debug("Indices: " + indices);
|
|
||||||
|
|
||||||
mLog.debug("Finished!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -58,7 +58,7 @@ public class ChannelizerViewer extends JFrame
|
|||||||
private int mChannelsPerRow;
|
private int mChannelsPerRow;
|
||||||
private long mToneFrequency;
|
private long mToneFrequency;
|
||||||
private DFTSize mMainPanelDFTSize = DFTSize.FFT32768;
|
private DFTSize mMainPanelDFTSize = DFTSize.FFT32768;
|
||||||
private DFTSize mChannelPanelDFTSize = DFTSize.FFT00512;
|
private DFTSize mChannelPanelDFTSize = DFTSize.FFT01024;
|
||||||
private TestTuner mTestTuner;
|
private TestTuner mTestTuner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,8 +73,7 @@ public class ChannelizerViewer extends JFrame
|
|||||||
mChannelCount = mTestTuner.getSampleRate() / CHANNEL_BANDWIDTH;
|
mChannelCount = mTestTuner.getSampleRate() / CHANNEL_BANDWIDTH;
|
||||||
mChannelsPerRow = channelsPerRow;
|
mChannelsPerRow = channelsPerRow;
|
||||||
|
|
||||||
mToneFrequency = mTestTuner.getTunerController().getFrequency() +
|
mToneFrequency = mTestTuner.getTunerController().getFrequency() + 19200;
|
||||||
(int)(CHANNEL_BANDWIDTH * 1); //Set to channel 1 as default
|
|
||||||
|
|
||||||
mTestTuner.setToneFrequency(mToneFrequency);
|
mTestTuner.setToneFrequency(mToneFrequency);
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import dsp.mixer.Oscillator;
|
|||||||
import org.jtransforms.fft.FloatFFT_1D;
|
import org.jtransforms.fft.FloatFFT_1D;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sample.complex.ComplexBuffer;
|
||||||
|
|
||||||
public class ComplexPolyphaseChannelizer extends AbstractComplexPolyphaseChannelizer
|
public class ComplexPolyphaseChannelizer extends AbstractComplexPolyphaseChannelizer
|
||||||
{
|
{
|
||||||
@@ -56,6 +57,16 @@ public class ComplexPolyphaseChannelizer extends AbstractComplexPolyphaseChannel
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected void filter(float inphase, float quadrature)
|
protected void filter(float inphase, float quadrature)
|
||||||
{
|
{
|
||||||
int index = (getChannelCount() - mFilterPointer - 1) * 2;
|
int index = (getChannelCount() - mFilterPointer - 1) * 2;
|
||||||
|
|||||||
@@ -18,14 +18,13 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package dsp.filter.channelizer;
|
package dsp.filter.channelizer;
|
||||||
|
|
||||||
import buffer.ReverseFloatCircularBuffer;
|
|
||||||
import dsp.filter.FilterFactory;
|
import dsp.filter.FilterFactory;
|
||||||
import dsp.filter.Window;
|
import dsp.filter.Window;
|
||||||
import dsp.filter.design.FilterDesignException;
|
import dsp.filter.design.FilterDesignException;
|
||||||
import dsp.mixer.Oscillator;
|
|
||||||
import org.jtransforms.fft.FloatFFT_1D;
|
import org.jtransforms.fft.FloatFFT_1D;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sample.complex.ComplexBuffer;
|
||||||
|
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
|
|
||||||
@@ -33,15 +32,16 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
|||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(ComplexPolyphaseChannelizerM2.class);
|
private final static Logger mLog = LoggerFactory.getLogger(ComplexPolyphaseChannelizerM2.class);
|
||||||
|
|
||||||
private static final int DEFAULT_CHANNEL_BANDWIDTH = 12500;
|
private static final int DEFAULT_MINIMUM_CHANNEL_BANDWIDTH = 12500;
|
||||||
|
|
||||||
private ReverseFloatCircularBuffer[] mDataBuffers;
|
private float[][] mSamples;
|
||||||
private float[][] mFilterCoefficients;
|
private float[][] mCoefficients;
|
||||||
private int mFilterPointer = 0;
|
private int[][] mSampleIndexMap;
|
||||||
|
private int mColumnPointer;
|
||||||
|
private int mRowPointer;
|
||||||
|
private int mBlockSize;
|
||||||
private FloatFFT_1D mFFT;
|
private FloatFFT_1D mFFT;
|
||||||
|
|
||||||
private DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0000");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Non-Maximally Decimated Polyphase Filter Bank (NMDPFB) channelizer that divides the input frequency band into
|
* Non-Maximally Decimated Polyphase Filter Bank (NMDPFB) channelizer that divides the input frequency band into
|
||||||
* equal bandwidth channels that are each oversampled by 2x for output.
|
* equal bandwidth channels that are each oversampled by 2x for output.
|
||||||
@@ -51,251 +51,299 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
|||||||
* filter 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
|
* 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
|
* @param sampleRate of the incoming sample stream
|
||||||
* output, this number must be even (divisible by the oversample rate).
|
* @param channelCount - number of filters/channels to output. Since this filter bank oversamples each filter
|
||||||
*
|
* output, this number must be even (divisible by the 2x oversample rate).
|
||||||
* @param channelSampleRate for each decimated, oversampled channel
|
|
||||||
*/
|
*/
|
||||||
public ComplexPolyphaseChannelizerM2(float[] taps, int channels, int channelSampleRate)
|
public ComplexPolyphaseChannelizerM2(float[] taps, int sampleRate, int channelCount)
|
||||||
{
|
{
|
||||||
super(channels, channelSampleRate);
|
super(sampleRate, channelCount);
|
||||||
|
|
||||||
if(channels % 2 != 0)
|
if(channelCount % 2 != 0)
|
||||||
{
|
{
|
||||||
throw new IllegalArgumentException("Channel count must be an even multiple of the oversample rate (2)");
|
throw new IllegalArgumentException("Channel count must be an even multiple of the oversample rate (2)");
|
||||||
}
|
}
|
||||||
|
|
||||||
initFilters(taps);
|
init(taps);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Non-Maximally Decimated Polyphase Filter Bank (NMDPFB) channelizer that divides the input frequency band into
|
* Non-Maximally Decimated Polyphase Filter Bank (NMDPFB) channelizer that divides the input frequency band into
|
||||||
* equal bandwidth channels that are each oversampled by 2x for output.
|
* equal bandwidth channels that are each oversampled by 2x for output.
|
||||||
*
|
*
|
||||||
* Note: this constructor uses a default channel bandwidth of 12.5 kHz and produces channels with an oversampled
|
* Note: this constructor uses a default minimum channel bandwidth of 12.5 kHz. The number of channels is calculate
|
||||||
* channel sample rate of 25.0 kHz.
|
* from the sample rate and this default minimum channel bandwidth. The resulting per-channel bandwidth will be
|
||||||
|
* between 12.5 and 25.0 kHz with the output per-channel sample rate of 2x the bandwidth.
|
||||||
*
|
*
|
||||||
* @param sampleRate to be channelized.
|
* @param sampleRate to be channelized.
|
||||||
|
* @param tapsPerFilter to use when designing the filter
|
||||||
*/
|
*/
|
||||||
private ComplexPolyphaseChannelizerM2(int sampleRate, int tapsPerFilter) throws FilterDesignException
|
public ComplexPolyphaseChannelizerM2(int sampleRate, int tapsPerFilter) throws FilterDesignException
|
||||||
{
|
{
|
||||||
super(sampleRate / DEFAULT_CHANNEL_BANDWIDTH, DEFAULT_CHANNEL_BANDWIDTH);
|
super(sampleRate, (int)(sampleRate / DEFAULT_MINIMUM_CHANNEL_BANDWIDTH));
|
||||||
|
|
||||||
mLog.debug("Sample Rate: " + sampleRate);
|
|
||||||
|
|
||||||
float[] filterTaps = FilterFactory.getSincChannelizer(getChannelSampleRate(), getChannelCount(),
|
float[] filterTaps = FilterFactory.getSincChannelizer(getChannelSampleRate(), getChannelCount(),
|
||||||
tapsPerFilter, Window.WindowType.BLACKMAN_HARRIS_7, true);
|
tapsPerFilter, Window.WindowType.BLACKMAN_HARRIS_7, true);
|
||||||
|
|
||||||
initFilters(filterTaps);
|
init(filterTaps);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Non-Maximally Decimated Polyphase Filter Bank (NMDPFB) channelizer with a default channel bandwidth of 12.5 kHz
|
|
||||||
* and a channel output sample rate of 25.0 kHz.
|
|
||||||
*
|
|
||||||
* @param sampleRate that is an even multiple of 25.0 kHz
|
|
||||||
* @return channelizer
|
|
||||||
* @throws IllegalArgumentException if the sample rate argument is not an even multiple of 25.0 kHz
|
|
||||||
*/
|
|
||||||
public static ComplexPolyphaseChannelizerM2 create(int sampleRate, int tapsPerFilter) throws IllegalArgumentException,
|
|
||||||
FilterDesignException
|
|
||||||
{
|
|
||||||
if(sampleRate % (2 * DEFAULT_CHANNEL_BANDWIDTH) != 0)
|
|
||||||
{
|
|
||||||
throw new IllegalArgumentException("Sample rate must be an even multiple of 25 kHz");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ComplexPolyphaseChannelizerM2(sampleRate, tapsPerFilter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates this channelizer to use the new sample rate. This method creates a new filter suitable for the
|
* Updates this channelizer to use the new sample rate. This method creates a new filter suitable for the
|
||||||
* sample rate and reinitializes all internal data structures to prepare for processing the new sample rate.
|
* sample rate and reinitializes all internal data structures to prepare for processing the new sample rate.
|
||||||
* @param sampleRate that is a multiple of 25.0 kHz
|
* @param sampleRate in hertz
|
||||||
* @throws IllegalArgumentException if the sample rate is not a multiple of 25.0 kHz
|
|
||||||
* @throws FilterDesignException if a filter cannot be designed for the target sample rate
|
|
||||||
*/
|
*/
|
||||||
public void updateSampleRate(int sampleRate) throws IllegalArgumentException, FilterDesignException
|
@Override
|
||||||
{
|
public void setSampleRate(int sampleRate)
|
||||||
if(sampleRate % (2 * DEFAULT_CHANNEL_BANDWIDTH) != 0)
|
|
||||||
{
|
|
||||||
throw new IllegalArgumentException("Sample rate must be an even multiple of 25 kHz");
|
|
||||||
}
|
|
||||||
|
|
||||||
float[] filterTaps = FilterFactory.getSincChannelizer(getChannelSampleRate(), getChannelCount(),
|
|
||||||
15, Window.WindowType.BLACKMAN_HARRIS_7, true);
|
|
||||||
|
|
||||||
initFilters(filterTaps);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filters the incoming complex sample pair
|
|
||||||
*
|
|
||||||
* @param inphase sample
|
|
||||||
* @param quadrature sample
|
|
||||||
*/
|
|
||||||
protected void filter(float inphase, float quadrature)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
mDataBuffers[mFilterPointer--].put(quadrature);
|
super.setSampleRate(sampleRate);
|
||||||
mDataBuffers[mFilterPointer].put(inphase);
|
|
||||||
|
|
||||||
if(mFilterPointer == 0)
|
float[] filterTaps = FilterFactory.getSincChannelizer(getChannelSampleRate(), getChannelCount(),
|
||||||
{
|
15, Window.WindowType.BLACKMAN_HARRIS_7, true);
|
||||||
calculate();
|
|
||||||
mFilterPointer = getChannelCount() * 2;
|
|
||||||
}
|
|
||||||
else if(mFilterPointer == getChannelCount())
|
|
||||||
{
|
|
||||||
calculate();
|
|
||||||
}
|
|
||||||
|
|
||||||
mFilterPointer--;
|
init(filterTaps);
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(FilterDesignException fde)
|
||||||
{
|
{
|
||||||
mLog.error("Error!", e);
|
throw new IllegalArgumentException("Cannot create a channelizer filter for the specified sample rate [" +
|
||||||
|
sampleRate + "]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void calculate()
|
|
||||||
{
|
|
||||||
float[] samples = new float[getChannelCount() * 2];
|
|
||||||
|
|
||||||
if(mFilterPointer == 0)
|
|
||||||
{
|
|
||||||
for(int x = 0; x < mFilterCoefficients.length; x++)
|
|
||||||
{
|
|
||||||
int index = x * 2;
|
|
||||||
|
|
||||||
//Inphase sample
|
|
||||||
samples[index] = convolve(mFilterCoefficients[x], mDataBuffers[index]);
|
|
||||||
|
|
||||||
//Quadrature sample
|
|
||||||
samples[index + 1] = convolve(mFilterCoefficients[x], mDataBuffers[index + 1]);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int half = mFilterCoefficients.length / 2;
|
|
||||||
|
|
||||||
for(int x = 0; x < half; x++)
|
|
||||||
{
|
|
||||||
int index = x * 2;
|
|
||||||
int filterIndex = x + half;
|
|
||||||
|
|
||||||
//Inphase sample
|
|
||||||
samples[index] = convolve(mFilterCoefficients[filterIndex], mDataBuffers[index]);
|
|
||||||
|
|
||||||
//Quadrature sample
|
|
||||||
samples[index + 1] = convolve(mFilterCoefficients[filterIndex], mDataBuffers[index + 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(int x = half; x < mFilterCoefficients.length; x++)
|
|
||||||
{
|
|
||||||
int index = x * 2;
|
|
||||||
int filterIndex = x - half;
|
|
||||||
|
|
||||||
//Inphase sample
|
|
||||||
samples[index] = convolve(mFilterCoefficients[filterIndex], mDataBuffers[index]);
|
|
||||||
|
|
||||||
//Quadrature sample
|
|
||||||
samples[index + 1] = convolve(mFilterCoefficients[filterIndex], mDataBuffers[index + 1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//IFFT is executed in-place with the output overwriting the input
|
|
||||||
mFFT.complexInverse(samples, true);
|
|
||||||
|
|
||||||
dispatch(samples);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters the samples contained in the buffer against the filter coefficients
|
* Receives the complex sample buffer and processes the results through the channelizer.
|
||||||
*
|
|
||||||
* @param coefficients of the sub filter
|
|
||||||
* @param buffer containing data samples
|
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
private float convolve(float[] coefficients, ReverseFloatCircularBuffer buffer)
|
@Override
|
||||||
|
public void receive(ComplexBuffer complexBuffer)
|
||||||
|
{
|
||||||
|
float[] samples = complexBuffer.getSamples();
|
||||||
|
|
||||||
|
int samplesPointer = 0;
|
||||||
|
|
||||||
|
while(samplesPointer < samples.length)
|
||||||
|
{
|
||||||
|
int lengthToCopy = mBlockSize - (mRowPointer % mBlockSize);
|
||||||
|
|
||||||
|
if((samplesPointer + lengthToCopy) > samples.length)
|
||||||
|
{
|
||||||
|
lengthToCopy = samples.length - samplesPointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.arraycopy(samples, samplesPointer, mSamples[mColumnPointer], mRowPointer, lengthToCopy);
|
||||||
|
samplesPointer += lengthToCopy;
|
||||||
|
mRowPointer += lengthToCopy;
|
||||||
|
|
||||||
|
if(mRowPointer % mBlockSize == 0)
|
||||||
|
{
|
||||||
|
process();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the sample buffer for each new block of sample data that is loaded and distributes the results to any
|
||||||
|
* registered channel listeners.
|
||||||
|
*/
|
||||||
|
private void process()
|
||||||
|
{
|
||||||
|
int sampleRowCount = getChannelCount() * 2;
|
||||||
|
|
||||||
|
float[] processed = new float[sampleRowCount];
|
||||||
|
|
||||||
|
|
||||||
|
//Top of block processing
|
||||||
|
if(mRowPointer >= sampleRowCount)
|
||||||
|
{
|
||||||
|
int[] sampleIndexMap = mSampleIndexMap[mColumnPointer];
|
||||||
|
|
||||||
|
for(int channel = 0; channel < getChannelCount(); channel++)
|
||||||
|
{
|
||||||
|
int outputIndex = 2 * (getChannelCount() - channel - 1);
|
||||||
|
int sampleIndex = 2 * channel;
|
||||||
|
|
||||||
|
float[] filter = mCoefficients[channel];
|
||||||
|
|
||||||
|
processed[outputIndex] = filter((sampleIndex), mCoefficients[channel], sampleIndexMap);
|
||||||
|
processed[outputIndex + 1] = filter((sampleIndex + 1), mCoefficients[channel], sampleIndexMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
mRowPointer = 0;
|
||||||
|
mColumnPointer--;
|
||||||
|
|
||||||
|
if(mColumnPointer < 0)
|
||||||
|
{
|
||||||
|
mColumnPointer = mSamples.length - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Mid-point processing
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int[] sampleIndexMap = mSampleIndexMap[mColumnPointer];
|
||||||
|
|
||||||
|
int half = getChannelCount() / 2;
|
||||||
|
|
||||||
|
for(int channel = 0; channel < half; channel++)
|
||||||
|
{
|
||||||
|
int outputIndex = 2 * (getChannelCount() - channel - 1);
|
||||||
|
int sampleIndex = 2 * channel;
|
||||||
|
int filterIndex = channel + half;
|
||||||
|
|
||||||
|
processed[outputIndex] = filter(sampleIndex, mCoefficients[filterIndex], sampleIndexMap);
|
||||||
|
processed[outputIndex + 1] = filter(sampleIndex + 1, mCoefficients[filterIndex], sampleIndexMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
//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)];
|
||||||
|
|
||||||
|
for(int channel = half; channel < getChannelCount(); channel++)
|
||||||
|
{
|
||||||
|
int outputIndex = 2 * (getChannelCount() - channel - 1);
|
||||||
|
int sampleIndex = 2 * channel;
|
||||||
|
int filterIndex = channel - half;
|
||||||
|
|
||||||
|
processed[outputIndex] = filter(sampleIndex, mCoefficients[filterIndex], sampleIndexMap);
|
||||||
|
processed[outputIndex + 1] = filter(sampleIndex + 1, mCoefficients[filterIndex], sampleIndexMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Rotate each of the channels to the correct phase using the IFFT
|
||||||
|
mFFT.complexInverse(processed, true);
|
||||||
|
|
||||||
|
dispatch(processed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the filtered output for polyphase filter arm.
|
||||||
|
*
|
||||||
|
* @param sampleRow index pointing to a row in the mSamples array to filter
|
||||||
|
* @param coefficients to use in filtering the samples
|
||||||
|
* @param sampleIndexMap to translate the column index offset based on the current sample column pointer
|
||||||
|
* @return filtered sample for the row
|
||||||
|
*/
|
||||||
|
private float filter(int sampleRow, float[] coefficients, int[] sampleIndexMap)
|
||||||
{
|
{
|
||||||
float accumulator = 0.0f;
|
float accumulator = 0.0f;
|
||||||
|
|
||||||
for(int x = 0; x < coefficients.length; x++)
|
for(int column = 0; column < mSamples.length; column++)
|
||||||
{
|
{
|
||||||
accumulator += coefficients[x] * buffer.get(x);
|
accumulator += mSamples[sampleIndexMap[column]][sampleRow] * coefficients[column];
|
||||||
}
|
}
|
||||||
|
|
||||||
return accumulator;
|
return accumulator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Distributes the filter taps to each of the polyphase filters
|
* Initializes the channelizer structures. Distributes the prototype filter coefficients to each of the polyphase
|
||||||
|
* filters, sets up the sample buffer array and pointers and initializes the FFT.
|
||||||
*
|
*
|
||||||
* @param taps
|
* These structures resemble those described by Fred Harris in Multirate Signal Processing for Communications
|
||||||
|
* Systems, p230-233. However, instead of swapping the input sample buffers and swapping the output sample array
|
||||||
|
* on every other processing block, we simply swap the filter coefficient banks and leave the input sample buffer
|
||||||
|
* and output array indexes aligned. However, the entire structure (sample buffer, coefficients and output array)
|
||||||
|
* is inverted (row zero index is M-1 and M-1 is zero index) to facilitate easy loading of data into the input
|
||||||
|
* sample buffer array using Java's efficient System.arrayCopy() utility.
|
||||||
|
*
|
||||||
|
* Note: the filter coefficients array is organized (row,column) for ease of accessing each filter arm row during
|
||||||
|
* convolution. The samples array is organized (column,row) for ease/efficiency in loading each row of data. Also,
|
||||||
|
* the input sample array implements a circular buffer where the mCoefficientsMap provides a lookup to each sample
|
||||||
|
* index based on the current value of the mColumnPointer.
|
||||||
|
*
|
||||||
|
* @param coefficients of the prototype filter for this channelizer
|
||||||
*/
|
*/
|
||||||
private void initFilters(float[] taps)
|
private void init(float[] coefficients)
|
||||||
{
|
{
|
||||||
int tapCount = (int)Math.ceil((double)taps.length / (double)getChannelCount());
|
int columnCount = (int)Math.ceil((double)coefficients.length / (double)getChannelCount());
|
||||||
|
int filterRowCount = getChannelCount();
|
||||||
|
int sampleRowCount = getChannelCount() * 2;
|
||||||
|
|
||||||
mDataBuffers = new ReverseFloatCircularBuffer[getChannelCount() * 2];
|
//Sample and filter coefficient arrays are specifically setup as column/row and row/column
|
||||||
|
mSamples = new float[columnCount][sampleRowCount];
|
||||||
|
mCoefficients = new float[filterRowCount][columnCount];
|
||||||
|
|
||||||
for(int x = 0; x < getChannelCount() * 2; x++)
|
mColumnPointer = columnCount - 1;
|
||||||
|
mBlockSize = getChannelCount();
|
||||||
|
mRowPointer = mBlockSize;
|
||||||
|
|
||||||
|
//Setup the FFT
|
||||||
|
mFFT = new FloatFFT_1D(getChannelCount());
|
||||||
|
|
||||||
|
//Setup the polyphase filters
|
||||||
|
|
||||||
|
//Setup the index map for the sample array circular buffer
|
||||||
|
initIndexMap(columnCount);
|
||||||
|
|
||||||
|
int coefficientIndex = 0;
|
||||||
|
|
||||||
|
//Load filter columns left-2-right (samples are loaded in reverse column order, right-2-left, for easy convolution)
|
||||||
|
for(int columnIndex = 0; columnIndex < columnCount; columnIndex++)
|
||||||
{
|
{
|
||||||
mDataBuffers[x] = new ReverseFloatCircularBuffer(tapCount);
|
//Load filter rows bottom to top, opposite of sample row loading and sample output ordering
|
||||||
}
|
for(int rowIndex = filterRowCount - 1; rowIndex >= 0; rowIndex--)
|
||||||
|
|
||||||
mFilterCoefficients = new float[getChannelCount()][tapCount];
|
|
||||||
|
|
||||||
for(int tap = 0; tap < tapCount; tap++)
|
|
||||||
{
|
|
||||||
for(int filter = 0; filter < getChannelCount(); filter++)
|
|
||||||
{
|
{
|
||||||
int index = tap * getChannelCount() + filter;
|
mCoefficients[rowIndex][columnIndex] = coefficients[coefficientIndex++];
|
||||||
|
|
||||||
if(index < taps.length)
|
|
||||||
{
|
|
||||||
mFilterCoefficients[filter][tap] = taps[index];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mFFT = new FloatFFT_1D(getChannelCount());
|
/**
|
||||||
mFilterPointer = getChannelCount() - 1; //Start at M/2
|
* Generates a circular buffer index map to support lookup of the translated index based on the current column
|
||||||
|
* pointer and the desired sample index.
|
||||||
|
*/
|
||||||
|
private void initIndexMap(int size)
|
||||||
|
{
|
||||||
|
mSampleIndexMap = new int[size][size];
|
||||||
|
|
||||||
|
for(int row = 0; row < size; row++)
|
||||||
|
{
|
||||||
|
for(int column = 0;column < size;column++)
|
||||||
|
{
|
||||||
|
int offset = row + column;
|
||||||
|
|
||||||
|
mSampleIndexMap[row][column] = ((offset < size) ? offset : offset - size);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args)
|
public static void main(String[] args)
|
||||||
{
|
{
|
||||||
mLog.debug("Starting ...");
|
mLog.debug("Starting ...");
|
||||||
|
|
||||||
int symbolRate = 4800;
|
int channelCount = 4;
|
||||||
int samplesPerSymbol = 2;
|
|
||||||
int channels = 800;
|
|
||||||
int channelBandwidth = 12500;
|
int channelBandwidth = 12500;
|
||||||
int sampleRate = channels * channelBandwidth;
|
int sampleRate = channelCount * channelBandwidth;
|
||||||
|
int tapsPerFilter = 14;
|
||||||
float[] taps = FilterFactory.getLowPass(10000000, 12500, 5600, Window.WindowType.BLACKMAN_HARRIS_7);
|
|
||||||
|
|
||||||
int channelCenter = (int)(12500 * 1.5);
|
int channelCenter = (int)(12500 * 1.5);
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
float[] taps = new float[16];
|
||||||
sb.append("\nPolyphase TunerChannelizer\n");
|
for(int x = 0; x < taps.length; x++)
|
||||||
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, channelBandwidth * 2);
|
|
||||||
|
|
||||||
Oscillator oscillator = new Oscillator(channelCenter, 2 * sampleRate);
|
|
||||||
|
|
||||||
for(int x = 0; x < 2000; x++)
|
|
||||||
{
|
{
|
||||||
channelizer.filter(oscillator.getComplex().inphase(), oscillator.getComplex().quadrature());
|
taps[x] = x;
|
||||||
oscillator.rotate();
|
}
|
||||||
|
float[] samples = new float[32];
|
||||||
|
for(int x = 0; x < samples.length; x++)
|
||||||
|
{
|
||||||
|
samples[x] = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// ComplexPolyphaseChannelizerM2 channelizer = new ComplexPolyphaseChannelizerM2(sampleRate, tapsPerFilter);
|
||||||
|
ComplexPolyphaseChannelizerM2 channelizer = new ComplexPolyphaseChannelizerM2(taps,sampleRate, channelCount);
|
||||||
|
|
||||||
|
// Oscillator oscillator = new Oscillator(channelCenter, 2 * sampleRate);
|
||||||
|
//
|
||||||
|
// ComplexBuffer complexBuffer = oscillator.generateComplexBuffer(1000);
|
||||||
|
ComplexBuffer complexBuffer = new ComplexBuffer(samples);
|
||||||
|
|
||||||
|
channelizer.receive(complexBuffer);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Error", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
mLog.debug("Finished!");
|
mLog.debug("Finished!");
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import dsp.filter.design.FilterDesignException;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sample.Listener;
|
import sample.Listener;
|
||||||
import source.ISourceEventProcessor;
|
|
||||||
import source.Source;
|
import source.Source;
|
||||||
import source.SourceEvent;
|
import source.SourceEvent;
|
||||||
import source.SourceException;
|
import source.SourceException;
|
||||||
@@ -45,7 +44,7 @@ public class PolyphaseChannelManager implements Listener<SourceEvent>
|
|||||||
|
|
||||||
private Tuner mTuner;
|
private Tuner mTuner;
|
||||||
private List<PolyphaseChannelSource> mChannelSources = new CopyOnWriteArrayList<>();
|
private List<PolyphaseChannelSource> mChannelSources = new CopyOnWriteArrayList<>();
|
||||||
private ChannelIndexCalculator mChannelIndexCalculator;
|
private ChannelCalculator mChannelCalculator;
|
||||||
private ComplexPolyphaseChannelizerM2 mPolyphaseChannelizer;
|
private ComplexPolyphaseChannelizerM2 mPolyphaseChannelizer;
|
||||||
private ChannelSourceEventListener mChannelSourceEventListener = new ChannelSourceEventListener();
|
private ChannelSourceEventListener mChannelSourceEventListener = new ChannelSourceEventListener();
|
||||||
private ScheduledBufferProcessor mBufferProcessor;
|
private ScheduledBufferProcessor mBufferProcessor;
|
||||||
@@ -81,10 +80,9 @@ public class PolyphaseChannelManager implements Listener<SourceEvent>
|
|||||||
long frequency = mTuner.getTunerController().getFrequency();
|
long frequency = mTuner.getTunerController().getFrequency();
|
||||||
int channelCount = sampleRate / CHANNEL_BANDWIDTH;
|
int channelCount = sampleRate / CHANNEL_BANDWIDTH;
|
||||||
|
|
||||||
mChannelIndexCalculator = new ChannelIndexCalculator(frequency, channelCount, CHANNEL_BANDWIDTH,
|
mChannelCalculator = new ChannelCalculator(sampleRate, channelCount, frequency, CHANNEL_OVERSAMPLING);
|
||||||
CHANNEL_OVERSAMPLING);
|
|
||||||
|
|
||||||
mBufferProcessor = new ScheduledBufferProcessor(500, 100, 100, 50);
|
mBufferProcessor = new ScheduledBufferProcessor(500, 100, 50, 50);
|
||||||
|
|
||||||
updateChannelizer(sampleRate, null);
|
updateChannelizer(sampleRate, null);
|
||||||
|
|
||||||
@@ -104,7 +102,7 @@ public class PolyphaseChannelManager implements Listener<SourceEvent>
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
List<Integer> polyphaseIndexes = mChannelIndexCalculator.getPolyphaseChannelIndexes(tunerChannel);
|
List<Integer> polyphaseIndexes = mChannelCalculator.getChannelIndexes(tunerChannel);
|
||||||
|
|
||||||
IPolyphaseChannelOutputProcessor outputProcessor = getOutputProcessor(polyphaseIndexes);
|
IPolyphaseChannelOutputProcessor outputProcessor = getOutputProcessor(polyphaseIndexes);
|
||||||
|
|
||||||
@@ -119,8 +117,8 @@ public class PolyphaseChannelManager implements Listener<SourceEvent>
|
|||||||
catch(IllegalArgumentException iae)
|
catch(IllegalArgumentException iae)
|
||||||
{
|
{
|
||||||
mLog.info("Can't provide DDC for " + tunerChannel.toString() + " due to channelizer frequency [" +
|
mLog.info("Can't provide DDC for " + tunerChannel.toString() + " due to channelizer frequency [" +
|
||||||
mChannelIndexCalculator.getCenterFrequency() + "] and sample rate [" +
|
mChannelCalculator.getCenterFrequency() + "] and sample rate [" +
|
||||||
(mChannelIndexCalculator.getChannelCount() * mChannelIndexCalculator.getChannelBandwidth()) + "]");
|
(mChannelCalculator.getChannelCount() * mChannelCalculator.getChannelBandwidth()) + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
return channelSource;
|
return channelSource;
|
||||||
@@ -198,7 +196,7 @@ public class PolyphaseChannelManager implements Listener<SourceEvent>
|
|||||||
{
|
{
|
||||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||||
//Update the index calculator and each of the output processors to update each channel frequency
|
//Update the index calculator and each of the output processors to update each channel frequency
|
||||||
mChannelIndexCalculator.setCenterFrequency(sourceEvent.getValue().longValue());
|
mChannelCalculator.setCenterFrequency(sourceEvent.getValue().longValue());
|
||||||
updateOutputProcessors(sourceEvent);
|
updateOutputProcessors(sourceEvent);
|
||||||
break;
|
break;
|
||||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||||
@@ -218,7 +216,7 @@ public class PolyphaseChannelManager implements Listener<SourceEvent>
|
|||||||
*/
|
*/
|
||||||
private void updateChannelizer(int sampleRate, SourceEvent sourceEvent)
|
private void updateChannelizer(int sampleRate, SourceEvent sourceEvent)
|
||||||
{
|
{
|
||||||
mChannelIndexCalculator.setSampleRate(sampleRate);
|
mChannelCalculator.setSampleRate(sampleRate);
|
||||||
|
|
||||||
//Lock on the buffer processor to block any channel create/start/stop requests while we configure
|
//Lock on the buffer processor to block any channel create/start/stop requests while we configure
|
||||||
synchronized(mBufferProcessor)
|
synchronized(mBufferProcessor)
|
||||||
@@ -233,14 +231,14 @@ public class PolyphaseChannelManager implements Listener<SourceEvent>
|
|||||||
{
|
{
|
||||||
if(mPolyphaseChannelizer == null)
|
if(mPolyphaseChannelizer == null)
|
||||||
{
|
{
|
||||||
mPolyphaseChannelizer = ComplexPolyphaseChannelizerM2.create(sampleRate,
|
mPolyphaseChannelizer = new ComplexPolyphaseChannelizerM2(sampleRate,
|
||||||
POLYPHASE_FILTER_TAPS_PER_CHANNEL);
|
POLYPHASE_FILTER_TAPS_PER_CHANNEL);
|
||||||
|
|
||||||
mBufferProcessor.setListener(mPolyphaseChannelizer);
|
mBufferProcessor.setListener(mPolyphaseChannelizer);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mPolyphaseChannelizer.updateSampleRate(sampleRate);
|
mPolyphaseChannelizer.setSampleRate(sampleRate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(IllegalArgumentException iae)
|
catch(IllegalArgumentException iae)
|
||||||
@@ -304,7 +302,7 @@ public class PolyphaseChannelManager implements Listener<SourceEvent>
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
List<Integer> indexes = mChannelIndexCalculator.getPolyphaseChannelIndexes(channelSource.getTunerChannel());
|
List<Integer> indexes = mChannelCalculator.getChannelIndexes(channelSource.getTunerChannel());
|
||||||
|
|
||||||
//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())
|
||||||
|
|||||||
@@ -106,10 +106,7 @@ public class ScheduledBufferProcessor<E> implements Listener<E>
|
|||||||
@Override
|
@Override
|
||||||
public void receive(E e)
|
public void receive(E e)
|
||||||
{
|
{
|
||||||
if(mRunning.get())
|
mQueue.offer(e);
|
||||||
{
|
|
||||||
mQueue.offer(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ public class TestTunerController extends TunerController
|
|||||||
|
|
||||||
int sweepRate = 0; //Hz per interval
|
int sweepRate = 0; //Hz per interval
|
||||||
|
|
||||||
mSampleGenerator = new SampleGenerator(DEFAULT_SAMPLE_RATE, 5000, SAMPLE_GENERATION_INTERVAL, sweepRate);
|
mSampleGenerator = new SampleGenerator(DEFAULT_SAMPLE_RATE, 12500, SAMPLE_GENERATION_INTERVAL, sweepRate);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user