From bf65e102867ae9dbc0feb7c8e7fbb1dbdfc55166 Mon Sep 17 00:00:00 2001 From: Denny Date: Sat, 18 Nov 2017 16:32:11 -0500 Subject: [PATCH] #39 WIP polyphase channelizer Updates the M2 channelizer and correctly implements the ChannelCalculator --- src/dsp/filter/FilterFactory.java | 22 +- .../AbstractComplexPolyphaseChannelizer.java | 50 +- .../filter/channelizer/ChannelCalculator.java | 588 ++++++++++++++++++ .../channelizer/ChannelIndexCalculator.java | 323 ---------- .../filter/channelizer/ChannelizerViewer.java | 5 +- .../ComplexPolyphaseChannelizer.java | 11 + .../ComplexPolyphaseChannelizerM2.java | 414 ++++++------ .../channelizer/PolyphaseChannelManager.java | 24 +- .../channelizer/ScheduledBufferProcessor.java | 5 +- .../tuner/test/TestTunerController.java | 2 +- 10 files changed, 882 insertions(+), 562 deletions(-) create mode 100644 src/dsp/filter/channelizer/ChannelCalculator.java delete mode 100644 src/dsp/filter/channelizer/ChannelIndexCalculator.java diff --git a/src/dsp/filter/FilterFactory.java b/src/dsp/filter/FilterFactory.java index b90b7986..0a8103cc 100644 --- a/src/dsp/filter/FilterFactory.java +++ b/src/dsp/filter/FilterFactory.java @@ -749,14 +749,14 @@ public class FilterFactory * * @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, - boolean logResults) throws FilterDesignException + public static float[] getSincChannelizer(double channelBandwidth, int channels, int tapsPerChannel, + WindowType windowType, boolean logResults) throws FilterDesignException { int currentTapsPerChannel = tapsPerChannel; - int sampleRate = channelBandwidth * channels * 2; + int sampleRate = (int)(channelBandwidth * (double)channels * 2.0); int filterLength = (channels * currentTapsPerChannel) - 1; - double cutoffFrequency = ((double)channelBandwidth / (double)sampleRate); + double cutoffFrequency = channelBandwidth / (double)sampleRate; double bandEdge = cutoffFrequency; double increment = cutoffFrequency * 0.1; @@ -831,6 +831,10 @@ public class FilterFactory 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) { mLog.debug("Polyphase Channelizer Filter Design Summary"); @@ -839,11 +843,11 @@ public class FilterFactory mLog.debug("Channel Bandwidth: " + channelBandwidth); mLog.debug("Channels: " + channels); mLog.debug("Window Type: " + windowType.name()); - mLog.debug("Taps Per Channel: " + tapsPerChannel + " (Requested:" + tapsPerChannel + ")"); - mLog.debug("Filter Length: " + (taps.length + 1)); + mLog.debug("Taps Per Channel - Requested:" + tapsPerChannel + " Actual:" + ((double)extendedTaps.length / (double)channels)); + mLog.debug("Filter Length: " + (extendedTaps.length)); mLog.debug("Requested Cutoff Frequency: " + (sampleRate * bandEdge)); 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.25 Channels: " + evaluate(taps, bandEdge * 1.25)); 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)); } - //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; } diff --git a/src/dsp/filter/channelizer/AbstractComplexPolyphaseChannelizer.java b/src/dsp/filter/channelizer/AbstractComplexPolyphaseChannelizer.java index 41cce4d0..5aee3dcf 100644 --- a/src/dsp/filter/channelizer/AbstractComplexPolyphaseChannelizer.java +++ b/src/dsp/filter/channelizer/AbstractComplexPolyphaseChannelizer.java @@ -36,18 +36,39 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener mSourceChangeBroadcaster = new Broadcaster(); private List mChannels = new CopyOnWriteArrayList<>(); + private int mSampleRate; private int mChannelCount; - private int mChannelSampleRate; + private double mChannelSampleRate; /** * Complex sample polyphase channelizer * * @param channelCount */ - public AbstractComplexPolyphaseChannelizer(int channelCount, int channelSampleRate) + public AbstractComplexPolyphaseChannelizer(int sampleRate, int 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 + * + ******************************************************************************/ +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 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 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 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"); + } +} diff --git a/src/dsp/filter/channelizer/ChannelIndexCalculator.java b/src/dsp/filter/channelizer/ChannelIndexCalculator.java deleted file mode 100644 index d7b6f28b..00000000 --- a/src/dsp/filter/channelizer/ChannelIndexCalculator.java +++ /dev/null @@ -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 - * - ******************************************************************************/ -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 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 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 indices = channelIndexCalculator.getPolyphaseChannelIndexes(tunerChannel); - - mLog.debug("Indices: " + indices); - - mLog.debug("Finished!"); - } -} diff --git a/src/dsp/filter/channelizer/ChannelizerViewer.java b/src/dsp/filter/channelizer/ChannelizerViewer.java index 482f8795..8afc76b6 100644 --- a/src/dsp/filter/channelizer/ChannelizerViewer.java +++ b/src/dsp/filter/channelizer/ChannelizerViewer.java @@ -58,7 +58,7 @@ public class ChannelizerViewer extends JFrame private int mChannelsPerRow; private long mToneFrequency; private DFTSize mMainPanelDFTSize = DFTSize.FFT32768; - private DFTSize mChannelPanelDFTSize = DFTSize.FFT00512; + private DFTSize mChannelPanelDFTSize = DFTSize.FFT01024; private TestTuner mTestTuner; /** @@ -73,8 +73,7 @@ public class ChannelizerViewer extends JFrame mChannelCount = mTestTuner.getSampleRate() / CHANNEL_BANDWIDTH; mChannelsPerRow = channelsPerRow; - mToneFrequency = mTestTuner.getTunerController().getFrequency() + - (int)(CHANNEL_BANDWIDTH * 1); //Set to channel 1 as default + mToneFrequency = mTestTuner.getTunerController().getFrequency() + 19200; mTestTuner.setToneFrequency(mToneFrequency); diff --git a/src/dsp/filter/channelizer/ComplexPolyphaseChannelizer.java b/src/dsp/filter/channelizer/ComplexPolyphaseChannelizer.java index c4977b1e..dca0f4ca 100644 --- a/src/dsp/filter/channelizer/ComplexPolyphaseChannelizer.java +++ b/src/dsp/filter/channelizer/ComplexPolyphaseChannelizer.java @@ -25,6 +25,7 @@ import dsp.mixer.Oscillator; import org.jtransforms.fft.FloatFFT_1D; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sample.complex.ComplexBuffer; public class ComplexPolyphaseChannelizer extends AbstractComplexPolyphaseChannelizer { @@ -56,6 +57,16 @@ public class ComplexPolyphaseChannelizer extends AbstractComplexPolyphaseChannel } @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) { int index = (getChannelCount() - mFilterPointer - 1) * 2; diff --git a/src/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java b/src/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java index 114b3417..ebd8eb5f 100644 --- a/src/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java +++ b/src/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java @@ -18,14 +18,13 @@ ******************************************************************************/ package dsp.filter.channelizer; -import buffer.ReverseFloatCircularBuffer; import dsp.filter.FilterFactory; import dsp.filter.Window; import dsp.filter.design.FilterDesignException; -import dsp.mixer.Oscillator; import org.jtransforms.fft.FloatFFT_1D; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sample.complex.ComplexBuffer; import java.text.DecimalFormat; @@ -33,15 +32,16 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann { 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[][] mFilterCoefficients; - private int mFilterPointer = 0; + private float[][] mSamples; + private float[][] mCoefficients; + private int[][] mSampleIndexMap; + private int mColumnPointer; + private int mRowPointer; + private int mBlockSize; 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 * 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 * 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 + * @param sampleRate of the incoming sample stream + * @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). */ - 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)"); } - initFilters(taps); + init(taps); } /** * 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. * - * Note: this constructor uses a default channel bandwidth of 12.5 kHz and produces channels with an oversampled - * channel sample rate of 25.0 kHz. + * Note: this constructor uses a default minimum channel bandwidth of 12.5 kHz. The number of channels is calculate + * 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 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); - - mLog.debug("Sample Rate: " + sampleRate); + super(sampleRate, (int)(sampleRate / DEFAULT_MINIMUM_CHANNEL_BANDWIDTH)); float[] filterTaps = FilterFactory.getSincChannelizer(getChannelSampleRate(), getChannelCount(), tapsPerFilter, Window.WindowType.BLACKMAN_HARRIS_7, true); - initFilters(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); + init(filterTaps); } /** * 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. - * @param sampleRate that is a multiple of 25.0 kHz - * @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 + * @param sampleRate in hertz */ - public void updateSampleRate(int sampleRate) throws IllegalArgumentException, FilterDesignException - { - 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) + @Override + public void setSampleRate(int sampleRate) { try { - mDataBuffers[mFilterPointer--].put(quadrature); - mDataBuffers[mFilterPointer].put(inphase); + super.setSampleRate(sampleRate); - if(mFilterPointer == 0) - { - calculate(); - mFilterPointer = getChannelCount() * 2; - } - else if(mFilterPointer == getChannelCount()) - { - calculate(); - } + float[] filterTaps = FilterFactory.getSincChannelizer(getChannelSampleRate(), getChannelCount(), + 15, Window.WindowType.BLACKMAN_HARRIS_7, true); - 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 - * - * @param coefficients of the sub filter - * @param buffer containing data samples - * @return + * Receives the complex sample buffer and processes the results through the channelizer. */ - 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; - 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; } + /** - * 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); - } - - mFilterCoefficients = new float[getChannelCount()][tapCount]; - - for(int tap = 0; tap < tapCount; tap++) - { - for(int filter = 0; filter < getChannelCount(); filter++) + //Load filter rows bottom to top, opposite of sample row loading and sample output ordering + for(int rowIndex = filterRowCount - 1; rowIndex >= 0; rowIndex--) { - int index = tap * getChannelCount() + filter; - - if(index < taps.length) - { - mFilterCoefficients[filter][tap] = taps[index]; - } + mCoefficients[rowIndex][columnIndex] = coefficients[coefficientIndex++]; } } + } - 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) { mLog.debug("Starting ..."); - int symbolRate = 4800; - int samplesPerSymbol = 2; - int channels = 800; + int channelCount = 4; int channelBandwidth = 12500; - int sampleRate = channels * channelBandwidth; - - float[] taps = FilterFactory.getLowPass(10000000, 12500, 5600, Window.WindowType.BLACKMAN_HARRIS_7); + int sampleRate = channelCount * channelBandwidth; + int tapsPerFilter = 14; int channelCenter = (int)(12500 * 1.5); - StringBuilder sb = new StringBuilder(); - 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, channelBandwidth * 2); - - Oscillator oscillator = new Oscillator(channelCenter, 2 * sampleRate); - - for(int x = 0; x < 2000; x++) + float[] taps = new float[16]; + for(int x = 0; x < taps.length; x++) { - channelizer.filter(oscillator.getComplex().inphase(), oscillator.getComplex().quadrature()); - oscillator.rotate(); + taps[x] = x; + } + 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!"); diff --git a/src/dsp/filter/channelizer/PolyphaseChannelManager.java b/src/dsp/filter/channelizer/PolyphaseChannelManager.java index 6bd797af..b82b212d 100644 --- a/src/dsp/filter/channelizer/PolyphaseChannelManager.java +++ b/src/dsp/filter/channelizer/PolyphaseChannelManager.java @@ -25,7 +25,6 @@ import dsp.filter.design.FilterDesignException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sample.Listener; -import source.ISourceEventProcessor; import source.Source; import source.SourceEvent; import source.SourceException; @@ -45,7 +44,7 @@ public class PolyphaseChannelManager implements Listener private Tuner mTuner; private List mChannelSources = new CopyOnWriteArrayList<>(); - private ChannelIndexCalculator mChannelIndexCalculator; + private ChannelCalculator mChannelCalculator; private ComplexPolyphaseChannelizerM2 mPolyphaseChannelizer; private ChannelSourceEventListener mChannelSourceEventListener = new ChannelSourceEventListener(); private ScheduledBufferProcessor mBufferProcessor; @@ -81,10 +80,9 @@ public class PolyphaseChannelManager implements Listener long frequency = mTuner.getTunerController().getFrequency(); int channelCount = sampleRate / CHANNEL_BANDWIDTH; - mChannelIndexCalculator = new ChannelIndexCalculator(frequency, channelCount, CHANNEL_BANDWIDTH, - CHANNEL_OVERSAMPLING); + mChannelCalculator = new ChannelCalculator(sampleRate, channelCount, frequency, CHANNEL_OVERSAMPLING); - mBufferProcessor = new ScheduledBufferProcessor(500, 100, 100, 50); + mBufferProcessor = new ScheduledBufferProcessor(500, 100, 50, 50); updateChannelizer(sampleRate, null); @@ -104,7 +102,7 @@ public class PolyphaseChannelManager implements Listener try { - List polyphaseIndexes = mChannelIndexCalculator.getPolyphaseChannelIndexes(tunerChannel); + List polyphaseIndexes = mChannelCalculator.getChannelIndexes(tunerChannel); IPolyphaseChannelOutputProcessor outputProcessor = getOutputProcessor(polyphaseIndexes); @@ -119,8 +117,8 @@ public class PolyphaseChannelManager implements Listener catch(IllegalArgumentException iae) { mLog.info("Can't provide DDC for " + tunerChannel.toString() + " due to channelizer frequency [" + - mChannelIndexCalculator.getCenterFrequency() + "] and sample rate [" + - (mChannelIndexCalculator.getChannelCount() * mChannelIndexCalculator.getChannelBandwidth()) + "]"); + mChannelCalculator.getCenterFrequency() + "] and sample rate [" + + (mChannelCalculator.getChannelCount() * mChannelCalculator.getChannelBandwidth()) + "]"); } return channelSource; @@ -198,7 +196,7 @@ public class PolyphaseChannelManager implements Listener { case NOTIFICATION_FREQUENCY_CHANGE: //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); break; case NOTIFICATION_SAMPLE_RATE_CHANGE: @@ -218,7 +216,7 @@ public class PolyphaseChannelManager implements Listener */ 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 synchronized(mBufferProcessor) @@ -233,14 +231,14 @@ public class PolyphaseChannelManager implements Listener { if(mPolyphaseChannelizer == null) { - mPolyphaseChannelizer = ComplexPolyphaseChannelizerM2.create(sampleRate, + mPolyphaseChannelizer = new ComplexPolyphaseChannelizerM2(sampleRate, POLYPHASE_FILTER_TAPS_PER_CHANNEL); mBufferProcessor.setListener(mPolyphaseChannelizer); } else { - mPolyphaseChannelizer.updateSampleRate(sampleRate); + mPolyphaseChannelizer.setSampleRate(sampleRate); } } catch(IllegalArgumentException iae) @@ -304,7 +302,7 @@ public class PolyphaseChannelManager implements Listener { try { - List indexes = mChannelIndexCalculator.getPolyphaseChannelIndexes(channelSource.getTunerChannel()); + List indexes = mChannelCalculator.getChannelIndexes(channelSource.getTunerChannel()); //If the indexes size is the same then update the current processor, otherwise create a new one if(channelSource.getPolyphaseChannelOutputProcessor().getInputChannelCount() == indexes.size()) diff --git a/src/dsp/filter/channelizer/ScheduledBufferProcessor.java b/src/dsp/filter/channelizer/ScheduledBufferProcessor.java index 17635504..adc7ac9b 100644 --- a/src/dsp/filter/channelizer/ScheduledBufferProcessor.java +++ b/src/dsp/filter/channelizer/ScheduledBufferProcessor.java @@ -106,10 +106,7 @@ public class ScheduledBufferProcessor implements Listener @Override public void receive(E e) { - if(mRunning.get()) - { - mQueue.offer(e); - } + mQueue.offer(e); } /** diff --git a/src/source/tuner/test/TestTunerController.java b/src/source/tuner/test/TestTunerController.java index 280f853a..b09f630b 100644 --- a/src/source/tuner/test/TestTunerController.java +++ b/src/source/tuner/test/TestTunerController.java @@ -51,7 +51,7 @@ public class TestTunerController extends TunerController 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 {