mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
#39 WIP - changes tuner controllers to expose the frequency controller
and the channel manager in preparation for polyphase.
This commit is contained in:
@@ -25,8 +25,8 @@ import sample.Listener;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import source.ISourceEventListener;
|
||||
import source.SourceEvent;
|
||||
import source.tuner.channel.polyphase.PolyphaseChannelSource;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
@@ -36,7 +36,7 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener<Co
|
||||
|
||||
private Broadcaster<SourceEvent> mSourceChangeBroadcaster = new Broadcaster();
|
||||
private List<PolyphaseChannelSource> mChannels = new CopyOnWriteArrayList<>();
|
||||
private int mSampleRate;
|
||||
private double mSampleRate;
|
||||
private int mChannelCount;
|
||||
private double mChannelSampleRate;
|
||||
|
||||
@@ -45,7 +45,7 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener<Co
|
||||
*
|
||||
* @param channelCount
|
||||
*/
|
||||
public AbstractComplexPolyphaseChannelizer(int sampleRate, int channelCount)
|
||||
public AbstractComplexPolyphaseChannelizer(double sampleRate, int channelCount)
|
||||
{
|
||||
mChannelCount = channelCount;
|
||||
mSampleRate = sampleRate;
|
||||
@@ -56,7 +56,7 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener<Co
|
||||
* Input sample rate for this channelizer
|
||||
* @return sample rate in hertz
|
||||
*/
|
||||
public int getSampleRate()
|
||||
public double getSampleRate()
|
||||
{
|
||||
return mSampleRate;
|
||||
}
|
||||
@@ -65,10 +65,10 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener<Co
|
||||
* Sets the input sample rate for for this channelizer
|
||||
* @param sampleRate in hertz
|
||||
*/
|
||||
public void setSampleRate(int sampleRate)
|
||||
public void setSampleRate(double sampleRate)
|
||||
{
|
||||
mSampleRate = sampleRate;
|
||||
mChannelSampleRate = (double)mSampleRate / (double)mChannelCount;
|
||||
mChannelSampleRate = mSampleRate / (double)mChannelCount;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,588 +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.channel.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,107 +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 sample.Broadcaster;
|
||||
import sample.Listener;
|
||||
import sample.complex.ComplexBuffer;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
|
||||
public class ChannelDistributor
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelDistributor.class);
|
||||
private Map<Integer,ComplexSampleAssembler> mListenerMap = new HashMap<>();
|
||||
private int mBufferSize;
|
||||
private int mChannelCount;
|
||||
|
||||
public ChannelDistributor(int bufferSize, int channelCount)
|
||||
{
|
||||
mBufferSize = bufferSize;
|
||||
mChannelCount = channelCount;
|
||||
|
||||
}
|
||||
|
||||
public void receive(float[] channels)
|
||||
{
|
||||
for(int channel = 0; channel < channels.length / 2; channel++)
|
||||
{
|
||||
if(mListenerMap.containsKey(channel))
|
||||
{
|
||||
ComplexSampleAssembler channelAssembler = mListenerMap.get(channel);
|
||||
|
||||
int index = channel * 2;
|
||||
channelAssembler.receive(channels[index], channels[index + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addListener(int channel, Listener<ComplexBuffer> listener)
|
||||
{
|
||||
ComplexSampleAssembler channelAssembler = mListenerMap.get(channel);
|
||||
|
||||
if(channelAssembler == null)
|
||||
{
|
||||
channelAssembler = new ComplexSampleAssembler(mBufferSize);
|
||||
mListenerMap.put(channel, channelAssembler);
|
||||
}
|
||||
|
||||
channelAssembler.addListener(listener);
|
||||
}
|
||||
|
||||
public class ComplexSampleAssembler
|
||||
{
|
||||
private Broadcaster<ComplexBuffer> mBroadcaster = new Broadcaster<>();
|
||||
private float[] mBuffer;
|
||||
private int mBufferPointer;
|
||||
private int mBufferSize;
|
||||
private boolean mEnableLogging;
|
||||
|
||||
public ComplexSampleAssembler(int bufferSize)
|
||||
{
|
||||
mBufferSize = bufferSize;
|
||||
mBuffer = new float[mBufferSize];
|
||||
}
|
||||
|
||||
public void receive(float inphase, float quadrature)
|
||||
{
|
||||
mBuffer[mBufferPointer++] = inphase;
|
||||
mBuffer[mBufferPointer++] = quadrature;
|
||||
|
||||
if(mBufferPointer >= mBufferSize)
|
||||
{
|
||||
float[] bufferCopy = new float[mBuffer.length];
|
||||
System.arraycopy(mBuffer,0, bufferCopy, 0, mBuffer.length);
|
||||
|
||||
mBroadcaster.receive(new ComplexBuffer(bufferCopy));
|
||||
|
||||
mBufferPointer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void addListener(Listener<ComplexBuffer> listener)
|
||||
{
|
||||
mBroadcaster.addListener(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,342 +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 net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sample.Listener;
|
||||
import sample.SampleType;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import settings.SettingsManager;
|
||||
import source.ISourceEventProcessor;
|
||||
import source.SourceEvent;
|
||||
import source.SourceException;
|
||||
import source.tuner.channel.TunerChannel;
|
||||
import source.tuner.channel.TunerChannelSource;
|
||||
import source.tuner.configuration.TunerConfigurationModel;
|
||||
import source.tuner.test.TestTuner;
|
||||
import spectrum.DFTProcessor;
|
||||
import spectrum.DFTSize;
|
||||
import spectrum.SpectrumPanel;
|
||||
import spectrum.converter.ComplexDecibelConverter;
|
||||
import util.ThreadPool;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import java.awt.*;
|
||||
|
||||
public class ChannelizerViewer extends JFrame
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelizerViewer.class);
|
||||
|
||||
private static final int CHANNEL_BANDWIDTH = 12500;
|
||||
private static final int CHANNEL_FFT_FRAME_RATE = 20;
|
||||
|
||||
private SettingsManager mSettingsManager = new SettingsManager(new TunerConfigurationModel());
|
||||
private JPanel mPrimaryPanel;
|
||||
private JPanel mControlPanel;
|
||||
private PrimarySpectrumPanel mPrimarySpectrumPanel;
|
||||
private ChannelArrayPanel mChannelPanel;
|
||||
private int mChannelCount;
|
||||
private int mChannelsPerRow;
|
||||
private long mToneFrequency;
|
||||
private DFTSize mMainPanelDFTSize = DFTSize.FFT32768;
|
||||
private DFTSize mChannelPanelDFTSize = DFTSize.FFT01024;
|
||||
private TestTuner mTestTuner;
|
||||
|
||||
/**
|
||||
* GUI Test utility for researching polyphase channelizers.
|
||||
*
|
||||
* @param channelsPerRow
|
||||
*/
|
||||
public ChannelizerViewer(int channelsPerRow)
|
||||
{
|
||||
mTestTuner = new TestTuner();
|
||||
|
||||
mChannelCount = (int)(mTestTuner.getTunerController().getSampleRate() / CHANNEL_BANDWIDTH);
|
||||
mChannelsPerRow = channelsPerRow;
|
||||
|
||||
mToneFrequency = mTestTuner.getTunerController().getFrequency() + 19200;
|
||||
|
||||
mTestTuner.getTunerController().setToneFrequency(mToneFrequency);
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setTitle("TunerChannelizer Viewer");
|
||||
setSize(1000, 800);
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||
setLocationRelativeTo(null);
|
||||
add(getPrimaryPanel());
|
||||
}
|
||||
|
||||
private JPanel getPrimaryPanel()
|
||||
{
|
||||
if(mPrimaryPanel == null)
|
||||
{
|
||||
mPrimaryPanel = new JPanel();
|
||||
mPrimaryPanel.setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[][][grow,fill]"));
|
||||
mPrimaryPanel.add(getSpectrumPanel(), "wrap");
|
||||
mPrimaryPanel.add(getControlPanel(), "wrap");
|
||||
mPrimaryPanel.add(getChannelArrayPanel());
|
||||
}
|
||||
|
||||
return mPrimaryPanel;
|
||||
}
|
||||
|
||||
private PrimarySpectrumPanel getSpectrumPanel()
|
||||
{
|
||||
if(mPrimarySpectrumPanel == null)
|
||||
{
|
||||
mPrimarySpectrumPanel = new PrimarySpectrumPanel(mSettingsManager,
|
||||
mTestTuner.getTunerController().getSampleRate());
|
||||
mPrimarySpectrumPanel.setPreferredSize(new Dimension(1000, 200));
|
||||
mPrimarySpectrumPanel.setDFTSize(mMainPanelDFTSize);
|
||||
mTestTuner.getSourceManager().addComplexBufferListener(mPrimarySpectrumPanel);
|
||||
}
|
||||
|
||||
return mPrimarySpectrumPanel;
|
||||
}
|
||||
|
||||
private JPanel getControlPanel()
|
||||
{
|
||||
if(mControlPanel == null)
|
||||
{
|
||||
mControlPanel = new JPanel();
|
||||
mControlPanel.setLayout(new MigLayout("insets 0 0 0 0", "", ""));
|
||||
|
||||
mControlPanel.add(new JLabel("Tone:"), "align left");
|
||||
int maximumFrequencyOffset = (int)(mTestTuner.getTunerController().getSampleRate() / 2);
|
||||
long minimumFrequency = mTestTuner.getTunerController().getFrequency() - maximumFrequencyOffset;
|
||||
long maximumFrequency = mTestTuner.getTunerController().getFrequency() + maximumFrequencyOffset;
|
||||
|
||||
SpinnerNumberModel model = new SpinnerNumberModel(mToneFrequency, minimumFrequency, maximumFrequency,
|
||||
100 );
|
||||
|
||||
model.addChangeListener(new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e)
|
||||
{
|
||||
int toneFrequency = model.getNumber().intValue();
|
||||
mTestTuner.getTunerController().setToneFrequency(toneFrequency);
|
||||
}
|
||||
});
|
||||
|
||||
JSpinner spinner = new JSpinner(model);
|
||||
|
||||
mControlPanel.add(spinner);
|
||||
mControlPanel.add(new JLabel("Hz"));
|
||||
|
||||
mControlPanel.add(new JLabel("Sample Rate: " + mTestTuner.getTunerController().getSampleRate() + " Hz"), "push,align right");
|
||||
|
||||
mControlPanel.add(new JLabel("Max: " + (mTestTuner.getTunerController().getSampleRate() / 2) + " Hz"), "push,align right");
|
||||
mControlPanel.add(new JLabel("Channels: " + mChannelCount), "push,align right");
|
||||
}
|
||||
|
||||
return mControlPanel;
|
||||
}
|
||||
|
||||
private ChannelArrayPanel getChannelArrayPanel()
|
||||
{
|
||||
if(mChannelPanel == null)
|
||||
{
|
||||
mChannelPanel = new ChannelArrayPanel();
|
||||
}
|
||||
|
||||
return mChannelPanel;
|
||||
}
|
||||
|
||||
public class ChannelArrayPanel extends JPanel
|
||||
{
|
||||
public ChannelArrayPanel()
|
||||
{
|
||||
int bufferSize = CHANNEL_BANDWIDTH / CHANNEL_FFT_FRAME_RATE;
|
||||
if(bufferSize % 2 == 1)
|
||||
{
|
||||
bufferSize++;
|
||||
}
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "fill", "fill"));
|
||||
|
||||
double spectralBandwidth = mTestTuner.getTunerController().getSampleRate();
|
||||
double halfSpectralBandwidth = spectralBandwidth / 2.0;
|
||||
|
||||
int channelToLog = -1;
|
||||
|
||||
for(int x = 0; x < mChannelCount; x++)
|
||||
{
|
||||
//place the channels left and right of 10.0 MHz
|
||||
long frequency = (long)(mTestTuner.getTunerController().getFrequency() +
|
||||
((x * CHANNEL_BANDWIDTH) - halfSpectralBandwidth) + (CHANNEL_BANDWIDTH / 2));
|
||||
|
||||
mLog.debug("Channel " + x + " Frequency: " + frequency);
|
||||
|
||||
ChannelPanel channelPanel = new ChannelPanel(mSettingsManager, CHANNEL_BANDWIDTH * 2, frequency, CHANNEL_BANDWIDTH, (x == channelToLog));
|
||||
channelPanel.setDFTSize(mChannelPanelDFTSize);
|
||||
|
||||
if(x % mChannelsPerRow == mChannelsPerRow - 1)
|
||||
{
|
||||
add(channelPanel, "grow,push,wrap 2px");
|
||||
}
|
||||
else
|
||||
{
|
||||
add(channelPanel, "grow,push");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class PrimarySpectrumPanel extends JPanel implements Listener<ComplexBuffer>, ISourceEventProcessor
|
||||
{
|
||||
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
|
||||
private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter();
|
||||
private SpectrumPanel mSpectrumPanel;
|
||||
|
||||
public PrimarySpectrumPanel(SettingsManager settingsManager, double sampleRate)
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||
mSpectrumPanel = new SpectrumPanel(settingsManager);
|
||||
add(mSpectrumPanel);
|
||||
|
||||
mDFTProcessor.addConverter(mComplexDecibelConverter);
|
||||
mDFTProcessor.process(SourceEvent.sampleRateChange(sampleRate));
|
||||
mComplexDecibelConverter.addListener(mSpectrumPanel);
|
||||
}
|
||||
|
||||
public void setDFTSize(DFTSize dftSize)
|
||||
{
|
||||
mDFTProcessor.setDFTSize(dftSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ComplexBuffer complexBuffer)
|
||||
{
|
||||
mDFTProcessor.receive(complexBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(SourceEvent event) throws SourceException
|
||||
{
|
||||
mLog.debug("Source Event! Add handler support for this to channelizer viewer");
|
||||
}
|
||||
}
|
||||
|
||||
public class ChannelPanel extends JPanel implements Listener<ComplexBuffer>, ISourceEventProcessor
|
||||
{
|
||||
private TunerChannelSource mSource;
|
||||
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
|
||||
private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter();
|
||||
private SpectrumPanel mSpectrumPanel;
|
||||
private long mFrequency;
|
||||
|
||||
public ChannelPanel(SettingsManager settingsManager, double sampleRate, long frequency, int bandwidth, boolean enableLogging)
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||
mSpectrumPanel = new SpectrumPanel(settingsManager);
|
||||
add(mSpectrumPanel);
|
||||
|
||||
mDFTProcessor.addConverter(mComplexDecibelConverter);
|
||||
mDFTProcessor.process(SourceEvent.sampleRateChange(sampleRate));
|
||||
|
||||
mComplexDecibelConverter.addListener(mSpectrumPanel);
|
||||
|
||||
mFrequency = frequency;
|
||||
|
||||
TunerChannel tunerChannel = new TunerChannel(frequency, bandwidth);
|
||||
mSource = mTestTuner.getSourceManager().getSource(tunerChannel);
|
||||
|
||||
if(mSource != null)
|
||||
{
|
||||
if(enableLogging)
|
||||
{
|
||||
mSource.setListener(new Listener<ComplexBuffer>()
|
||||
{
|
||||
@Override
|
||||
public void receive(ComplexBuffer complexBuffer)
|
||||
{
|
||||
mLog.debug("Sending samples to DFT processor: " + complexBuffer.getSamples().length);
|
||||
mDFTProcessor.receive(complexBuffer);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
mSource.setListener(mDFTProcessor);
|
||||
}
|
||||
|
||||
mSource.start();
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Couldn't get a source from the tuner for frequency: " + frequency);
|
||||
}
|
||||
}
|
||||
|
||||
public TunerChannelSource getSource()
|
||||
{
|
||||
return mSource;
|
||||
}
|
||||
|
||||
public void setDFTSize(DFTSize dftSize)
|
||||
{
|
||||
mDFTProcessor.setDFTSize(dftSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ComplexBuffer complexBuffer)
|
||||
{
|
||||
mLog.debug("Got a buffer for the channel dft processor");
|
||||
mDFTProcessor.receive(complexBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(SourceEvent event) throws SourceException
|
||||
{
|
||||
mLog.debug("Source Event! Add handler support for this to channelizer viewer");
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
int channelsPerRow = 4;
|
||||
|
||||
final ChannelizerViewer frame = new ChannelizerViewer(channelsPerRow);
|
||||
|
||||
EventQueue.invokeLater(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
frame.setVisible(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -78,7 +78,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
* @param sampleRate to be channelized.
|
||||
* @param tapsPerFilter to use when designing the filter
|
||||
*/
|
||||
public ComplexPolyphaseChannelizerM2(int sampleRate, int tapsPerFilter) throws FilterDesignException
|
||||
public ComplexPolyphaseChannelizerM2(double sampleRate, int tapsPerFilter) throws FilterDesignException
|
||||
{
|
||||
super(sampleRate, (int)(sampleRate / DEFAULT_MINIMUM_CHANNEL_BANDWIDTH));
|
||||
|
||||
@@ -94,7 +94,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
* @param sampleRate in hertz
|
||||
*/
|
||||
@Override
|
||||
public void setSampleRate(int sampleRate)
|
||||
public void setSampleRate(double sampleRate)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -1,366 +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 dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor;
|
||||
import dsp.filter.channelizer.output.OneChannelOutputProcessor;
|
||||
import dsp.filter.channelizer.output.TwoChannelOutputProcessor;
|
||||
import dsp.filter.design.FilterDesignException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sample.Listener;
|
||||
import sample.complex.IComplexBufferProvider;
|
||||
import source.Source;
|
||||
import source.SourceEvent;
|
||||
import source.SourceException;
|
||||
import source.tuner.channel.TunerChannel;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public class PolyphaseChannelManager implements Listener<SourceEvent>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
|
||||
private static final int CHANNEL_BANDWIDTH = 12500;
|
||||
private static final double CHANNEL_OVERSAMPLING = 2.0;
|
||||
private static final int CHANNEL_SAMPLE_RATE = (int)(CHANNEL_BANDWIDTH * CHANNEL_OVERSAMPLING);
|
||||
private static final int POLYPHASE_FILTER_TAPS_PER_CHANNEL = 17;
|
||||
|
||||
private IComplexBufferProvider mComplexBufferProvider;
|
||||
private List<PolyphaseChannelSource> mChannelSources = new CopyOnWriteArrayList<>();
|
||||
private ChannelCalculator mChannelCalculator;
|
||||
private ComplexPolyphaseChannelizerM2 mPolyphaseChannelizer;
|
||||
private ChannelSourceEventListener mChannelSourceEventListener = new ChannelSourceEventListener();
|
||||
private ScheduledBufferProcessor mBufferProcessor;
|
||||
|
||||
/**
|
||||
* Polyphase Channel Manager is a DDC channel manager and complex buffer queue/processor for a tuner. This class
|
||||
* provides DDC polyphase channel sources and wraps a polyphase channelizer processing sample buffers produced by
|
||||
* the tuner and distributing channelized sample buffers to each allocated DDC polyphase channel source. This
|
||||
* class is responsible for monitoring the tuner for changes in center frequency and/or sample rate and updating
|
||||
* active DDC polyphase channel sources accordingly. This class also monitors source event requests and
|
||||
* notifications received from active DDC polyphase channel sources to adjust sample streams as required.
|
||||
*
|
||||
* Note: add this channel manager as a source event listener to the complex buffer provider to ensure this manager
|
||||
* adapts to changes in source frequency and sample rate.
|
||||
*
|
||||
* @param complexBufferProvider that supports register/deregister of this channel manager
|
||||
* @param frequency of the provided complex buffer samples
|
||||
* @param sampleRate of the provided complex buffer samples
|
||||
*/
|
||||
public PolyphaseChannelManager(IComplexBufferProvider complexBufferProvider, long frequency, int sampleRate)
|
||||
{
|
||||
if(complexBufferProvider == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Complex buffer provider argument cannot be null");
|
||||
}
|
||||
|
||||
mComplexBufferProvider = complexBufferProvider;
|
||||
|
||||
//Ensure that sample rate is a multiple of channel sample rate since polyphase channelizer is M2
|
||||
if(sampleRate % (CHANNEL_SAMPLE_RATE) != 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Tuner sample rate [" + sampleRate + "] must be an even multiple of " +
|
||||
CHANNEL_SAMPLE_RATE + " Hz");
|
||||
}
|
||||
|
||||
int channelCount = sampleRate / CHANNEL_BANDWIDTH;
|
||||
|
||||
mChannelCalculator = new ChannelCalculator(sampleRate, channelCount, frequency, CHANNEL_OVERSAMPLING);
|
||||
|
||||
mBufferProcessor = new ScheduledBufferProcessor(500, 100, 50, 50);
|
||||
|
||||
updateChannelizer(sampleRate, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a Digital Drop Channel (DDC) for the specified tuner channel or returns null if the channel can't be
|
||||
* sourced due to the current center frequency and/or sample rate.
|
||||
* @param tunerChannel specifying center frequency and bandwidth.
|
||||
* @return source or null.
|
||||
*/
|
||||
public Source getChannel(TunerChannel tunerChannel)
|
||||
{
|
||||
PolyphaseChannelSource channelSource = null;
|
||||
|
||||
try
|
||||
{
|
||||
List<Integer> polyphaseIndexes = mChannelCalculator.getChannelIndexes(tunerChannel);
|
||||
|
||||
IPolyphaseChannelOutputProcessor outputProcessor = getOutputProcessor(polyphaseIndexes);
|
||||
|
||||
if(outputProcessor != null)
|
||||
{
|
||||
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
|
||||
CHANNEL_SAMPLE_RATE);
|
||||
|
||||
mChannelSources.add(channelSource);
|
||||
}
|
||||
}
|
||||
catch(IllegalArgumentException iae)
|
||||
{
|
||||
mLog.info("Can't provide DDC for " + tunerChannel.toString() + " due to channelizer frequency [" +
|
||||
mChannelCalculator.getCenterFrequency() + "] and sample rate [" +
|
||||
(mChannelCalculator.getChannelCount() * mChannelCalculator.getChannelBandwidth()) + "]");
|
||||
}
|
||||
|
||||
return channelSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a processor to process the channelizer channel indexes into a composite output stream providing
|
||||
* channelized complex sample buffers to a registered source listener.
|
||||
* @param indexes to target by the output processor
|
||||
* @return output processor compatible with the number of indexes to monitor
|
||||
*/
|
||||
private IPolyphaseChannelOutputProcessor getOutputProcessor(List<Integer> indexes)
|
||||
{
|
||||
switch(indexes.size())
|
||||
{
|
||||
case 1:
|
||||
return new OneChannelOutputProcessor(CHANNEL_SAMPLE_RATE, indexes);
|
||||
case 2:
|
||||
return new TwoChannelOutputProcessor(CHANNEL_SAMPLE_RATE, indexes);
|
||||
default:
|
||||
//TODO: create output processor for greater than 2 input channels
|
||||
mLog.error("Request to create an output processor for unexpected channel index size:" + indexes.size());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts/adds the channel source to receive channelized sample buffers, registering with the tuner to receive
|
||||
* sample buffers when this is the first channel.
|
||||
*
|
||||
* @param channelSource to start
|
||||
*/
|
||||
private void startChannelSource(PolyphaseChannelSource channelSource)
|
||||
{
|
||||
synchronized(mBufferProcessor)
|
||||
{
|
||||
mPolyphaseChannelizer.addChannel(channelSource);
|
||||
|
||||
if(mPolyphaseChannelizer.getRegisteredChannelCount() == 1)
|
||||
{
|
||||
mComplexBufferProvider.addComplexBufferListener(mBufferProcessor);
|
||||
mBufferProcessor.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops/removes the channel source from receiving channelized sample buffers and deregisters from the tuner
|
||||
* when this is the last channel being sourced.
|
||||
*
|
||||
* @param channelSource to stop
|
||||
*/
|
||||
private void stopChannelSource(PolyphaseChannelSource channelSource)
|
||||
{
|
||||
synchronized(mBufferProcessor)
|
||||
{
|
||||
mChannelSources.remove(channelSource);
|
||||
|
||||
if(mPolyphaseChannelizer.getRegisteredChannelCount() == 0)
|
||||
{
|
||||
mComplexBufferProvider.removeComplexBufferListener(mBufferProcessor);
|
||||
mBufferProcessor.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the Listener<SourceEvent> interface for receiving notifications of frequency and sample rate
|
||||
* changes from the tuner.
|
||||
*/
|
||||
@Override
|
||||
public void receive(SourceEvent sourceEvent)
|
||||
{
|
||||
switch(sourceEvent.getEvent())
|
||||
{
|
||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||
//Update the index calculator and each of the output processors to update each channel frequency
|
||||
mChannelCalculator.setCenterFrequency(sourceEvent.getValue().longValue());
|
||||
updateOutputProcessors(sourceEvent);
|
||||
break;
|
||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||
//Update the channelizer (and the output processors) for the new sample rate
|
||||
updateChannelizer(sourceEvent.getValue().intValue(), sourceEvent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or updates the channelizer to process the incoming sample rate and updates any channel processors.
|
||||
*
|
||||
* @param sampleRate to use for the channelizer
|
||||
* @param sourceEvent containing a notification of sample rate change (optional - can be null). This source
|
||||
* event will be passed to all output processors to ensure the change is fully promulgated before restarting the
|
||||
* buffer processor if it was already running.
|
||||
*/
|
||||
private void updateChannelizer(int sampleRate, SourceEvent sourceEvent)
|
||||
{
|
||||
mChannelCalculator.setSampleRate(sampleRate);
|
||||
|
||||
//Lock on the buffer processor to block any channel create/start/stop requests while we configure
|
||||
synchronized(mBufferProcessor)
|
||||
{
|
||||
//Get current processor state so we can restart it after we update the channelizer
|
||||
boolean bufferProcessorWasRunning = mBufferProcessor.isRunning();
|
||||
|
||||
//Stop will be ignored if it is not currently running
|
||||
mBufferProcessor.stop();
|
||||
|
||||
try
|
||||
{
|
||||
if(mPolyphaseChannelizer == null)
|
||||
{
|
||||
mPolyphaseChannelizer = new ComplexPolyphaseChannelizerM2(sampleRate,
|
||||
POLYPHASE_FILTER_TAPS_PER_CHANNEL);
|
||||
|
||||
mBufferProcessor.setListener(mPolyphaseChannelizer);
|
||||
}
|
||||
else
|
||||
{
|
||||
mPolyphaseChannelizer.setSampleRate(sampleRate);
|
||||
}
|
||||
}
|
||||
catch(IllegalArgumentException iae)
|
||||
{
|
||||
mLog.error("Could not create polyphase channelizer for sample rate [" + sampleRate + "]", iae);
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
{
|
||||
mLog.error("Could not create filter for polyphase channelizer for sample rate [" + sampleRate + "]", fde);
|
||||
}
|
||||
|
||||
if(sourceEvent != null && sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_SAMPLE_RATE_CHANGE)
|
||||
{
|
||||
updateOutputProcessors(sourceEvent);
|
||||
}
|
||||
|
||||
if(bufferProcessorWasRunning)
|
||||
{
|
||||
mBufferProcessor.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates each of the output processors for any changes in the tuner's center frequency or sample rate, which
|
||||
* would cause the output processors to change the polyphase channelizer results channel(s) that the processor is
|
||||
* consuming
|
||||
*
|
||||
* @param sourceEvent that requires an update to the output processors
|
||||
*/
|
||||
private void updateOutputProcessors(SourceEvent sourceEvent)
|
||||
{
|
||||
for(PolyphaseChannelSource channelSource: mChannelSources)
|
||||
{
|
||||
updateOutputProcessor(channelSource);
|
||||
|
||||
//Notify the channel source that the frequency or sample rate has changed so that it can reset
|
||||
//any frequency correction value.
|
||||
if(sourceEvent != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
channelSource.process(sourceEvent);
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
mLog.error("Error while notifying polyphase channel source of a source event", se);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the polyphase channel source's output processor due to a change in the center frequency or sample
|
||||
* rate for the source providing sample buffers to the polyphase channelizer, or whenever the DDC channel's
|
||||
* center tuned frequency changes.
|
||||
*
|
||||
* @param channelSource that requires an update to its output processor
|
||||
*/
|
||||
private void updateOutputProcessor(PolyphaseChannelSource channelSource)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<Integer> indexes = mChannelCalculator.getChannelIndexes(channelSource.getTunerChannel());
|
||||
|
||||
long centerFrequency = mChannelCalculator.getCenterFrequencyForIndexes(indexes);
|
||||
|
||||
//If the indexes size is the same then update the current processor, otherwise create a new one
|
||||
if(channelSource.getPolyphaseChannelOutputProcessor().getInputChannelCount() == indexes.size())
|
||||
{
|
||||
channelSource.getPolyphaseChannelOutputProcessor().setPolyphaseChannelIndices(indexes);
|
||||
channelSource.setFrequency(centerFrequency);
|
||||
}
|
||||
else
|
||||
{
|
||||
channelSource.setPolyphaseChannelOutputProcessor(getOutputProcessor(indexes), centerFrequency);
|
||||
}
|
||||
}
|
||||
catch(IllegalArgumentException iae)
|
||||
{
|
||||
mLog.error("Error updating polyphase channel source - can't determine output channel indexes for " +
|
||||
"updated tuner center frequency and sample rate. Stopping channel source", iae);
|
||||
|
||||
stopChannelSource(channelSource);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal class for handling requests for start/stop sample stream from polyphase channel sources
|
||||
*/
|
||||
private class ChannelSourceEventListener implements Listener<SourceEvent>
|
||||
{
|
||||
@Override
|
||||
public void receive(SourceEvent sourceEvent)
|
||||
{
|
||||
switch(sourceEvent.getEvent())
|
||||
{
|
||||
case REQUEST_START_SAMPLE_STREAM:
|
||||
if(sourceEvent.hasSource() && sourceEvent.getSource() instanceof PolyphaseChannelSource)
|
||||
{
|
||||
startChannelSource((PolyphaseChannelSource)sourceEvent.getSource());
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Request to start sample stream for unrecognized source: " +
|
||||
(sourceEvent.hasSource() ? sourceEvent.getSource().getClass() : "null source"));
|
||||
}
|
||||
break;
|
||||
case REQUEST_STOP_SAMPLE_STREAM:
|
||||
if(sourceEvent.hasSource() && sourceEvent.getSource() instanceof PolyphaseChannelSource)
|
||||
{
|
||||
stopChannelSource((PolyphaseChannelSource)sourceEvent.getSource());
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Request to stop sample stream for unrecognized source: " +
|
||||
(sourceEvent.hasSource() ? sourceEvent.getSource().getClass() : "null source"));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
mLog.error("Received unrecognized source event from polyphase channel source [" +
|
||||
sourceEvent.getEvent() + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,225 +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 dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sample.Listener;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import sample.complex.ComplexToComplexBufferAssembler;
|
||||
import sample.real.IOverflowListener;
|
||||
import source.SourceEvent;
|
||||
import source.tuner.channel.TunerChannel;
|
||||
import source.tuner.channel.TunerChannelSource;
|
||||
|
||||
public class PolyphaseChannelSource extends TunerChannelSource
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class);
|
||||
|
||||
private ComplexToComplexBufferAssembler mComplexBufferAssembler = new ComplexToComplexBufferAssembler(2500);
|
||||
private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor;
|
||||
private double mChannelSampleRate;
|
||||
private long mCenterFrequency;
|
||||
private long mFrequencyCorrection;
|
||||
|
||||
/**
|
||||
* Polyphase channelizer tuner channel source implementation. Adapts the channel array output samples from the
|
||||
* polyphase channelizer into a single channel, or a channel synthesized from two adjacent channels that is
|
||||
* frequency translated and decimated to a single channel.
|
||||
*
|
||||
* @param tunerChannel describing the desired channel frequency and bandwidth/minimum sample rate
|
||||
* @param outputProcessor - to process polyphase channelizer channel results into a channel stream
|
||||
* @param producerSourceEventListener to receive source event requests (e.g. start/stop sample stream)
|
||||
* @param channelSampleRate for the downstream sample output
|
||||
*/
|
||||
public PolyphaseChannelSource(TunerChannel tunerChannel, IPolyphaseChannelOutputProcessor outputProcessor,
|
||||
Listener<SourceEvent> producerSourceEventListener, double channelSampleRate)
|
||||
{
|
||||
super(producerSourceEventListener, tunerChannel);
|
||||
mPolyphaseChannelOutputProcessor = outputProcessor;
|
||||
mChannelSampleRate = channelSampleRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive complex sample buffers from this channel source
|
||||
*/
|
||||
@Override
|
||||
public void setListener(final Listener<ComplexBuffer> listener)
|
||||
{
|
||||
mComplexBufferAssembler.setListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving complex sample buffers from this channel source
|
||||
*/
|
||||
@Override
|
||||
public void removeListener(Listener<ComplexBuffer> listener)
|
||||
{
|
||||
mComplexBufferAssembler.setListener(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel output processor used by this channel source to convert polyphase channel results into a specific
|
||||
* channel complex buffer output stream.
|
||||
*/
|
||||
public IPolyphaseChannelOutputProcessor getPolyphaseChannelOutputProcessor()
|
||||
{
|
||||
return mPolyphaseChannelOutputProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets/updates the output processor for this channel source, replacing the existing output processor.
|
||||
*
|
||||
* @param outputProcessor to use
|
||||
* @param frequency center for the channels processed by the output processor
|
||||
*/
|
||||
public void setPolyphaseChannelOutputProcessor(IPolyphaseChannelOutputProcessor outputProcessor, long frequency)
|
||||
{
|
||||
//Lock on the channel output processor to block the channel results processor thread from servicing the
|
||||
//channel output processor's buffer while we change out the processors.
|
||||
synchronized(mPolyphaseChannelOutputProcessor)
|
||||
{
|
||||
IPolyphaseChannelOutputProcessor existingProcessor = mPolyphaseChannelOutputProcessor;
|
||||
|
||||
//Swap out the processor so that incoming samples can accumulate in the new channel output processor
|
||||
mPolyphaseChannelOutputProcessor = outputProcessor;
|
||||
|
||||
//Remove the buffer overflow listener and clear any previous buffer overflow state
|
||||
existingProcessor.setSourceOverflowListener(null);
|
||||
broadcastOverflowState(false);
|
||||
|
||||
//Register to receive buffer overflow notifications so we can push them up through the source chain
|
||||
mPolyphaseChannelOutputProcessor.setSourceOverflowListener(this);
|
||||
|
||||
//Fully process the residual channel results buffer of the previous channel output processor
|
||||
if(existingProcessor != null)
|
||||
{
|
||||
existingProcessor.processChannelResults(mComplexBufferAssembler);
|
||||
}
|
||||
|
||||
//Finally, setup the frequency offset for the output processor and reset the frequency correction value
|
||||
//to allow consumers to adjust and calculate a new frequency correction value, if needed.
|
||||
mCenterFrequency = frequency;
|
||||
mFrequencyCorrection = 0;
|
||||
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
|
||||
broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mFrequencyCorrection));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary method for receiving channel results output from a polyphase channelizer. The results array will be
|
||||
* queued for processing to extract the target channel samples, process them for frequency correction and/or
|
||||
* channel aggregation, and dispatch the results to the downstream sample listener/consumer.
|
||||
*
|
||||
* @param channelResultsBuffer containing the polyphase channelizer output for a single complex sample period.
|
||||
*/
|
||||
public void receiveChannelResults(float[] channelResultsBuffer)
|
||||
{
|
||||
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Downstream channel sample rate
|
||||
*
|
||||
* @return sample rate in Hertz
|
||||
*/
|
||||
@Override
|
||||
public double getSampleRate()
|
||||
{
|
||||
return mChannelSampleRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the downstream channel sample rate
|
||||
* @param sampleRate in hertz
|
||||
*/
|
||||
@Override
|
||||
protected void setSampleRate(double sampleRate)
|
||||
{
|
||||
mChannelSampleRate = sampleRate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
mLog.info("Polyphase channel source received request to dispose");
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFrequencyCorrection()
|
||||
{
|
||||
return mFrequencyCorrection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the frequency correction value that is being applied to the channelized output stream by the
|
||||
* polyphase channel output processor.
|
||||
*
|
||||
* @param value to apply for frequency correction in hertz
|
||||
*/
|
||||
protected void setFrequencyCorrection(long value)
|
||||
{
|
||||
mFrequencyCorrection = value;
|
||||
updateFrequencyOffset();
|
||||
broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mFrequencyCorrection));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the center frequency for the incoming sample stream channel results and resets frequency correction to zero.
|
||||
* @param frequency in hertz
|
||||
*/
|
||||
@Override
|
||||
protected void setFrequency(long frequency)
|
||||
{
|
||||
mCenterFrequency = frequency;
|
||||
setFrequencyCorrection(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processSamples()
|
||||
{
|
||||
//Lock on the output processor so that it can't be changed out in the middle of processing
|
||||
synchronized(mPolyphaseChannelOutputProcessor)
|
||||
{
|
||||
mPolyphaseChannelOutputProcessor.processChannelResults(mComplexBufferAssembler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the frequency offset required to mix the incoming signal to the desired output baseband
|
||||
*/
|
||||
private long getFrequencyOffset()
|
||||
{
|
||||
return getTunerChannel().getFrequency() - mCenterFrequency + mFrequencyCorrection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the frequency offset being applied by the output processor. Calculates the offset using the center
|
||||
* frequency of the incoming polyphase channel results and the desired output channel frequency adjusted by any
|
||||
* requested frequency correction value.
|
||||
*/
|
||||
private void updateFrequencyOffset()
|
||||
{
|
||||
synchronized(mPolyphaseChannelOutputProcessor)
|
||||
{
|
||||
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,171 +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 sample.Listener;
|
||||
import sample.OverflowableTransferQueue;
|
||||
import sample.real.IOverflowListener;
|
||||
import util.ThreadPool;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class ScheduledBufferProcessor<E> implements Listener<E>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ScheduledBufferProcessor.class);
|
||||
|
||||
private OverflowableTransferQueue<E> mQueue;
|
||||
private Listener<E> mListener;
|
||||
private ScheduledFuture<?> mScheduledFuture;
|
||||
private long mDistributionInterval;
|
||||
private int mMaxBuffersPerInterval;
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
|
||||
/**
|
||||
* Scheduled Buffer Processor combines an internal overflowable buffer with a scheduled runnable processing task
|
||||
* for periodically distributing internally queued elements to the registered listener. This processor provides
|
||||
* a convenient way to create a thread-safe buffer for receiving elements from one thread/runnable and then
|
||||
* distributing those elements to a registered listener where distribution occurs on a separate scheduled thread
|
||||
* pool runnable thread. This allows the calling input thread to quickly return without incurring any subsequent
|
||||
* processing workload.
|
||||
*
|
||||
* The internal queue is an overflowable queue implementation that allows a listener to be registered to receive
|
||||
* notifications of overflow and reset state. Queue sizing parameters are specified in the constructor.
|
||||
*
|
||||
* @param maximumSize of the internal queue (overflow happens when this is exceeded)
|
||||
* @param resetThreshold of the internal queue (overflow reset happens once queue size falls below this threshold
|
||||
* @param distributionInterval in milliseconds specifying how often the internal scheduled thread pool buffer
|
||||
* distributor should run
|
||||
* @param maxBuffersPerInterval maximum number of buffers to pull from the internal queue during each distribution
|
||||
* interval
|
||||
*/
|
||||
public ScheduledBufferProcessor(int maximumSize, int resetThreshold, long distributionInterval, int maxBuffersPerInterval)
|
||||
{
|
||||
mQueue = new OverflowableTransferQueue<>(maximumSize, resetThreshold);
|
||||
|
||||
mQueue.setOverflowListener(new IOverflowListener()
|
||||
{
|
||||
@Override
|
||||
public void sourceOverflow(boolean overflow)
|
||||
{
|
||||
mLog.error("Overflow state changed - overflow:" + overflow);
|
||||
}
|
||||
});
|
||||
mDistributionInterval = distributionInterval;
|
||||
mMaxBuffersPerInterval = maxBuffersPerInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the listener to receive notifications of buffer overflow and/or reset. Note: this method can also be
|
||||
* invoked with a null argument to clear the previously registered overflow listener.
|
||||
*
|
||||
* @param listener to receive overflow/reset notifications.
|
||||
*/
|
||||
public void setOverflowListener(IOverflowListener listener)
|
||||
{
|
||||
mQueue.setOverflowListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or changes the listener to receive buffers from this processor.
|
||||
* @param listener to receive buffers
|
||||
*/
|
||||
public void setListener(Listener<E> listener)
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary input method for adding buffers to this processor. Note: incoming buffers will be ignored if this
|
||||
* processor is in a stopped state. You must invoke start() to allow incoming buffers and initiate buffer
|
||||
* processing.
|
||||
*
|
||||
* @param e to enqueue for distribution to a registered listener
|
||||
*/
|
||||
@Override
|
||||
public void receive(E e)
|
||||
{
|
||||
mQueue.offer(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts this buffer processor and allows queuing of incoming buffers.
|
||||
*/
|
||||
public void start()
|
||||
{
|
||||
if(mRunning.compareAndSet(false, true))
|
||||
{
|
||||
mScheduledFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new Processor(), 0, mDistributionInterval, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops this buffer processor, stops queuing of incoming buffers and clears the internal buffer queue.
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
if(mRunning.compareAndSet(true, false))
|
||||
{
|
||||
if(mScheduledFuture != null)
|
||||
{
|
||||
mScheduledFuture.cancel(true);
|
||||
mScheduledFuture = null;
|
||||
}
|
||||
|
||||
mQueue.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this processor is currently running
|
||||
*/
|
||||
public boolean isRunning()
|
||||
{
|
||||
return mRunning.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processor to service the buffer queue and distribute the buffers to the registered listener
|
||||
*/
|
||||
class Processor implements Runnable
|
||||
{
|
||||
private List<E> mBuffers = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
mQueue.drainTo(mBuffers, mMaxBuffersPerInterval);
|
||||
|
||||
if(mListener != null)
|
||||
{
|
||||
for(E buffer: mBuffers)
|
||||
{
|
||||
mListener.receive(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
mBuffers.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,170 +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.output;
|
||||
|
||||
import dsp.mixer.Oscillator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sample.OverflowableTransferQueue;
|
||||
import sample.complex.Complex;
|
||||
import sample.complex.ComplexSampleListener;
|
||||
import sample.real.IOverflowListener;
|
||||
import source.Source;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputProcessor
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelOutputProcessor.class);
|
||||
|
||||
private OverflowableTransferQueue<float[]> mChannelResultsQueue;
|
||||
private List<float[]> mChannelResultsToProcess = new ArrayList<>();
|
||||
private int mMaxResultsToProcess;
|
||||
|
||||
private int mInputChannelCount;
|
||||
//TODO: swap this out and use the LowPhaseNoiseOscillator
|
||||
private Oscillator mFrequencyCorrectionMixer;
|
||||
private boolean mFrequencyCorrectionEnabled;
|
||||
|
||||
/**
|
||||
* Base class for polyphase channelizer output channel processing. Provides built-in frequency translation
|
||||
* oscillator support to apply frequency correction to the channel sample stream as requested by sample consumer.
|
||||
*
|
||||
* @param inputChannelCount is the number of input channels for this output processor
|
||||
* @param sampleRate of the output channel. This is used to match the oscillator's sample rate to the output
|
||||
* channel sample rate for frequency translation/correction.
|
||||
*/
|
||||
public ChannelOutputProcessor(int inputChannelCount, double sampleRate)
|
||||
{
|
||||
mInputChannelCount = inputChannelCount;
|
||||
mFrequencyCorrectionMixer = new Oscillator(0, sampleRate);
|
||||
mMaxResultsToProcess = (int)(sampleRate / 10) * 2; //process at 100 millis interval, twice the expected inflow rate
|
||||
|
||||
mChannelResultsQueue = new OverflowableTransferQueue<>((int)(sampleRate * 3), (int)(sampleRate * 0.5));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the frequency offset to apply to the incoming samples to mix the desired signal to baseband.
|
||||
* @param frequencyOffset in hertz
|
||||
*/
|
||||
@Override
|
||||
public void setFrequencyOffset(long frequencyOffset)
|
||||
{
|
||||
mFrequencyCorrectionMixer.setFrequency(frequencyOffset);
|
||||
mFrequencyCorrectionEnabled = (frequencyOffset != 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveChannelResults(float[] channelResults)
|
||||
{
|
||||
mChannelResultsQueue.offer(channelResults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes all enqueued polyphase channelizer results until the internal queue is empty
|
||||
* @param listener to receive the processed channel results
|
||||
*/
|
||||
@Override
|
||||
public void processChannelResults(ComplexSampleListener listener)
|
||||
{
|
||||
try
|
||||
{
|
||||
int count = mChannelResultsQueue.drainTo(mChannelResultsToProcess, mMaxResultsToProcess);
|
||||
|
||||
if(count > 0)
|
||||
{
|
||||
process(mChannelResultsToProcess, listener);
|
||||
}
|
||||
}
|
||||
catch(Throwable throwable)
|
||||
{
|
||||
mLog.error("Error while processing polyphase channel samples", throwable);
|
||||
}
|
||||
|
||||
mChannelResultsToProcess.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-class implementation to process one polyphase channelizer result array.
|
||||
* @param channelResults to process
|
||||
* @param listener to receive the channel complex sample
|
||||
*/
|
||||
public abstract void process(List<float[]> channelResults, ComplexSampleListener listener);
|
||||
|
||||
|
||||
/**
|
||||
* Sets the overflow listener to monitor the internal channelizer channel results queue overflow state
|
||||
*/
|
||||
public void setSourceOverflowListener(Source source)
|
||||
{
|
||||
mChannelResultsQueue.setSourceOverflowListener(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the overflow listener from monitoring the internal channelizer channel results queue overflow state
|
||||
*/
|
||||
public void removeOverflowListener(IOverflowListener listener)
|
||||
{
|
||||
mChannelResultsQueue.setOverflowListener(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInputChannelCount()
|
||||
{
|
||||
return mInputChannelCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides frequency correction of the inphase component or simply returns the inphase argument value when no
|
||||
* frequency correction value has been specified.
|
||||
*
|
||||
* @param inphase value of the sample to translate/correct
|
||||
* @param quadrature value of the sample to translate/correct
|
||||
* @return corrected inphase value
|
||||
*/
|
||||
protected float getFrequencyCorrectedInphase(float inphase, float quadrature)
|
||||
{
|
||||
return Complex.multiplyInphase(inphase, quadrature, mFrequencyCorrectionMixer.inphase(),
|
||||
mFrequencyCorrectionMixer.quadrature());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides frequency correction of the quadrature component or simply returns the quadrature argument value when no
|
||||
* frequency correction value has been applied,
|
||||
*
|
||||
* @param inphase value of the sample to translate/correct
|
||||
* @param quadrature value of the sample to translate/correct
|
||||
* @return translated inphase value
|
||||
*/
|
||||
protected float getFrequencyCorrectedQuadrature(float inphase, float quadrature)
|
||||
{
|
||||
return Complex.multiplyQuadrature(inphase, quadrature, mFrequencyCorrectionMixer.inphase(),
|
||||
mFrequencyCorrectionMixer.quadrature());
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this channel has a frequency correction offset
|
||||
*/
|
||||
public boolean hasFrequencyCorrection()
|
||||
{
|
||||
return mFrequencyCorrectionEnabled;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,67 +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.output;
|
||||
|
||||
import sample.complex.ComplexSampleListener;
|
||||
import sample.real.IOverflowListener;
|
||||
import source.Source;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface IPolyphaseChannelOutputProcessor
|
||||
{
|
||||
/**
|
||||
* Receive and enqueue output results from the polyphase analysis channelizer
|
||||
* @param channelResults to enqueue
|
||||
*/
|
||||
void receiveChannelResults(float[] channelResults);
|
||||
|
||||
/**
|
||||
* Process the channel output channel results queue and deliver the output to the listener
|
||||
*
|
||||
* @param listener to receive the processed channel results
|
||||
*/
|
||||
void processChannelResults(ComplexSampleListener listener);
|
||||
|
||||
/**
|
||||
* Sets the desired frequency offset from center. The samples will be mixed with an oscillator set to this offset
|
||||
* frequency to produce an output where the desired signal is centered in the passband.
|
||||
*
|
||||
* @param frequency in hertz
|
||||
*/
|
||||
void setFrequencyOffset(long frequency);
|
||||
|
||||
/**
|
||||
* Indicates the number of input channels processed by this output processor
|
||||
* @return
|
||||
*/
|
||||
int getInputChannelCount();
|
||||
|
||||
/**
|
||||
* Updates the input polyphase channel index(es) used by this output processor
|
||||
* @param indexes
|
||||
*/
|
||||
void setPolyphaseChannelIndices(List<Integer> indexes);
|
||||
|
||||
/**
|
||||
* Sets the listener to receive buffer overflow notifications
|
||||
* @param source to receive overflow notifications
|
||||
*/
|
||||
void setSourceOverflowListener(Source source);
|
||||
}
|
||||
@@ -1,97 +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.output;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sample.complex.ComplexSampleListener;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class OneChannelOutputProcessor extends ChannelOutputProcessor
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(OneChannelOutputProcessor.class);
|
||||
|
||||
private int mChannelOffset;
|
||||
|
||||
/**
|
||||
* Processor to extract a single channel from a polyphase channelizer and produce an output I/Q complex sample
|
||||
* from each polyphase channelizer output results array.
|
||||
*
|
||||
* @param sampleRate of the output sample stream.
|
||||
* @param channelIndexes containing a single channel index.
|
||||
*/
|
||||
public OneChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes)
|
||||
{
|
||||
super(1, sampleRate);
|
||||
setPolyphaseChannelIndices(channelIndexes);
|
||||
mLog.debug("Sample Rate:" + sampleRate + " PolyPhase Channel Indexes:" + channelIndexes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates this processor to extract a single, specified channel index.
|
||||
* @param indexes containing a single channel index value.
|
||||
* @throws IllegalArgumentException if the list of indexes does not contain a single channel index.
|
||||
*/
|
||||
public void setPolyphaseChannelIndices(List<Integer> indexes)
|
||||
{
|
||||
if(indexes.size() != 1)
|
||||
{
|
||||
throw new IllegalArgumentException("Single channel output processor requires a single index to " +
|
||||
"process - provided indexes " + indexes.toString());
|
||||
}
|
||||
|
||||
//Set the channelized output results offset to twice the channel index to account for each channel having
|
||||
//an I/Q pair
|
||||
mChannelOffset = indexes.get(0) * 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the channel from the channel results array, apply frequency translation, and deliver the
|
||||
* extracted frequency-corrected channel I/Q sample set to the complex sample listener.
|
||||
*
|
||||
* @param channelResults to process containing a list of channel array of I/Q sample pairs (I0,Q0,I1,Q1...In,Qn)
|
||||
* @param listener to receive the extracted, frequency-translated channel results
|
||||
*/
|
||||
@Override
|
||||
public void process(List<float[]> channelResults, ComplexSampleListener listener)
|
||||
{
|
||||
for(float[] channels: channelResults)
|
||||
{
|
||||
if(channels.length < mChannelOffset + 1)
|
||||
{
|
||||
throw new IllegalArgumentException("Polyphase channelizer output channels array is not large enough to " +
|
||||
"cover this single channel output processor with channel offset [" + mChannelOffset + "]");
|
||||
}
|
||||
|
||||
float i = channels[mChannelOffset];
|
||||
float q = channels[mChannelOffset + 1];
|
||||
|
||||
if(hasFrequencyCorrection())
|
||||
{
|
||||
listener.receive(getFrequencyCorrectedInphase(i, q), getFrequencyCorrectedQuadrature(i, q));
|
||||
}
|
||||
else
|
||||
{
|
||||
listener.receive(i, q);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +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.output;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sample.complex.ComplexSampleListener;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TwoChannelOutputProcessor extends ChannelOutputProcessor
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(TwoChannelOutputProcessor.class);
|
||||
|
||||
private int mChannelOffset1;
|
||||
private int mChannelOffset2;
|
||||
|
||||
/**
|
||||
* Processor to extract two channels from a polyphase channelizer, synthesize/recombine the channels, apply
|
||||
* frequency translation and frequency correction, down-sample by a factor of two and output an I/Q complex sample.
|
||||
*
|
||||
* @param sampleRate of the output sample stream.
|
||||
* @param channelIndexes containing two channel indices.
|
||||
*/
|
||||
public TwoChannelOutputProcessor(int sampleRate, List<Integer> channelIndexes)
|
||||
{
|
||||
//Set the frequency correction oscillator to 2 x output sample rate since we'll be correcting the frequency
|
||||
//after synthesizing both input channels
|
||||
super(2, sampleRate * 2);
|
||||
setPolyphaseChannelIndices(channelIndexes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates this processor to extract the two specified channel indexes.
|
||||
*
|
||||
* @param indexes containing a single channel index value.
|
||||
* @throws IllegalArgumentException if the list of indexes does not contain a single channel index.
|
||||
*/
|
||||
public void setPolyphaseChannelIndices(List<Integer> indexes)
|
||||
{
|
||||
if(indexes.size() != 2)
|
||||
{
|
||||
throw new IllegalArgumentException("Double channel output processor requires two indexes to " +
|
||||
"process - provided indexes " + indexes.toString());
|
||||
}
|
||||
|
||||
//Set the channelized output results offsets to twice the channel index to account for each channel having
|
||||
//an I/Q pair
|
||||
mChannelOffset1 = indexes.get(0) * 2;
|
||||
mChannelOffset2 = indexes.get(1) * 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the channel from the channel results array, apply frequency translation, and deliver the
|
||||
* extracted frequency-corrected channel I/Q sample set to the complex sample listener.
|
||||
*
|
||||
* @param channelResults to process containing an array of channel I/Q sample pairs (I0,Q0,I1,Q1...In,Qn)
|
||||
* @param listener to receive the extracted, frequency-translated channel results
|
||||
*/
|
||||
@Override
|
||||
public void process(List<float[]> channelResults, ComplexSampleListener listener)
|
||||
{
|
||||
mLog.debug("I got channel results - doing nothing !!");
|
||||
// if(channels.length < mChannelOffset + 1)
|
||||
// {
|
||||
// throw new IllegalArgumentException("Polyphase channelizer output channels array is not large enough to " +
|
||||
// "cover this single channel output processor with channel offset [" + mChannelOffset + "]");
|
||||
// }
|
||||
//
|
||||
// float i = channels[mChannelOffset];
|
||||
// float q = channels[mChannelOffset + 1];
|
||||
|
||||
// listener.receive(getFrequencyCorrectedInphase(i, q), getFrequencyCorrectedQuadrature(i, q));
|
||||
}
|
||||
}
|
||||
@@ -611,8 +611,7 @@ public class ProcessingChain implements IChannelEventListener
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops processing if the chain is currently processing. Invocations on
|
||||
* an already stopped chain have no effect.
|
||||
* Stops processing if the chain is currently processing. Invocations on an already stopped chain have no effect.
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
@@ -639,15 +638,14 @@ public class ProcessingChain implements IChannelEventListener
|
||||
((RealSource) mSource).removeListener(mFilteredRealBufferBroadcaster);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized source "
|
||||
+ "sample type - cannot start processing chain");
|
||||
throw new IllegalArgumentException("Unrecognized source sample type - cannot start processing " +
|
||||
"chain");
|
||||
}
|
||||
|
||||
/* Release the source */
|
||||
mSource.dispose();
|
||||
mSource = null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,18 +22,8 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sample.Broadcaster;
|
||||
import sample.Listener;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import sample.complex.IComplexBufferProvider;
|
||||
import source.ISourceEventProcessor;
|
||||
import source.SourceEvent;
|
||||
import source.SourceException;
|
||||
import source.tuner.manager.AbstractSourceManager;
|
||||
import source.tuner.manager.TunerSourceManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
/**
|
||||
* Tuner - provides tuner channel sources, representing a channel frequency
|
||||
@@ -45,7 +35,6 @@ public abstract class Tuner
|
||||
protected Broadcaster<TunerEvent> mTunerEventBroadcaster = new Broadcaster<>();
|
||||
private TunerController mTunerController;
|
||||
private String mName;
|
||||
private AbstractSourceManager mTunerSourceManager;
|
||||
|
||||
/**
|
||||
* Abstract tuner class.
|
||||
@@ -57,8 +46,6 @@ public abstract class Tuner
|
||||
mName = name;
|
||||
mTunerController = tunerController;
|
||||
|
||||
mTunerSourceManager = new TunerSourceManager(mTunerController);
|
||||
|
||||
//Rebroadcast frequency and sample rate change events as tuner events
|
||||
mTunerController.addListener(new ISourceEventProcessor()
|
||||
{
|
||||
@@ -80,14 +67,6 @@ public abstract class Tuner
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Source Manager. Provides access to registering for complex buffer samples and source event notifications.
|
||||
*/
|
||||
public AbstractSourceManager getSourceManager()
|
||||
{
|
||||
return mTunerSourceManager;
|
||||
}
|
||||
|
||||
public TunerController getTunerController()
|
||||
{
|
||||
return mTunerController;
|
||||
|
||||
@@ -21,25 +21,23 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sample.Broadcaster;
|
||||
import sample.Listener;
|
||||
import sample.complex.Complex;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import sample.complex.IComplexBufferProvider;
|
||||
import source.ISourceEventListener;
|
||||
import source.ISourceEventProcessor;
|
||||
import source.SourceEvent;
|
||||
import source.SourceEvent.Event;
|
||||
import source.SourceEventListenerToProcessorAdapter;
|
||||
import source.SourceException;
|
||||
import source.tuner.channel.CICTunerChannelSource;
|
||||
import source.tuner.channel.ChannelManager;
|
||||
import source.tuner.channel.cic.CICTunerChannelSource;
|
||||
import source.tuner.channel.TunerChannel;
|
||||
import source.tuner.channel.TunerChannelSource;
|
||||
import source.tuner.channel.polyphase.PolyphaseChannelManager;
|
||||
import source.tuner.configuration.TunerConfiguration;
|
||||
import source.tuner.frequency.FrequencyController;
|
||||
import source.tuner.frequency.FrequencyController.Tunable;
|
||||
import source.tuner.channel.FrequencyController;
|
||||
import source.tuner.channel.FrequencyController.Tunable;
|
||||
|
||||
import java.util.SortedSet;
|
||||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
public abstract class TunerController implements Tunable, ISourceEventProcessor, ISourceEventListener,
|
||||
IComplexBufferProvider, Listener<ComplexBuffer>
|
||||
@@ -49,9 +47,8 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
|
||||
|
||||
/* List of currently tuned channels being served to demod channels */
|
||||
private SortedSet<TunerChannel> mTunedChannels = new ConcurrentSkipListSet<>();
|
||||
protected FrequencyController mFrequencyController;
|
||||
private int mMiddleUnusable;
|
||||
private double mUsableBandwidthPercentage;
|
||||
private FrequencyController mFrequencyController;
|
||||
private ChannelManager mChannelManager;
|
||||
private Listener<SourceEvent> mSourceEventListener;
|
||||
|
||||
/**
|
||||
@@ -65,14 +62,32 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
|
||||
* @throws SourceException - for any issues related to constructing the
|
||||
* class, tuning a frequency, or setting the bandwidth
|
||||
*/
|
||||
public TunerController(long minimumFrequency, long maximumFrequency, int middleUnusable, double usableBandwidth)
|
||||
public TunerController(long minimumFrequency, long maximumFrequency, long middleUnusable, double usableBandwidth)
|
||||
{
|
||||
mFrequencyController = new FrequencyController(this, minimumFrequency, maximumFrequency, 0.0d);
|
||||
mMiddleUnusable = middleUnusable;
|
||||
mUsableBandwidthPercentage = usableBandwidth;
|
||||
mFrequencyController = new FrequencyController(this, minimumFrequency, maximumFrequency,
|
||||
0.0d, middleUnusable, usableBandwidth);
|
||||
|
||||
mChannelManager = new PolyphaseChannelManager(mFrequencyController, this);
|
||||
|
||||
mSourceEventListener = new SourceEventListenerToProcessorAdapter(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Frequency controller for managing frequency and sample rate.
|
||||
*/
|
||||
public FrequencyController getFrequencyController()
|
||||
{
|
||||
return mFrequencyController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel manager for managing Digital Drop Chanel (DDC) tuner channel sources
|
||||
*/
|
||||
public ChannelManager getChannelManager()
|
||||
{
|
||||
return mChannelManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the ISourceEventListener interface to receive requests from sample consumers
|
||||
*/
|
||||
@@ -96,18 +111,18 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
|
||||
switch(sourceEvent.getEvent())
|
||||
{
|
||||
case REQUEST_FREQUENCY_CHANGE:
|
||||
setFrequency( sourceEvent.getValue().longValue() );
|
||||
getFrequencyController().setFrequency( sourceEvent.getValue().longValue() );
|
||||
break;
|
||||
case REQUEST_START_SAMPLE_STREAM:
|
||||
if(sourceEvent.getSource() instanceof Listener)
|
||||
if(sourceEvent.getSource() instanceof CICTunerChannelSource)
|
||||
{
|
||||
addComplexBufferListener((Listener<ComplexBuffer>)sourceEvent.getSource());
|
||||
addComplexBufferListener((CICTunerChannelSource)sourceEvent.getSource());
|
||||
}
|
||||
break;
|
||||
case REQUEST_STOP_SAMPLE_STREAM:
|
||||
if(sourceEvent.getSource() instanceof Listener)
|
||||
if(sourceEvent.getSource() instanceof CICTunerChannelSource)
|
||||
{
|
||||
removeComplexBufferListener((Listener<ComplexBuffer>)sourceEvent.getSource());
|
||||
removeComplexBufferListener((CICTunerChannelSource)sourceEvent.getSource());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -115,285 +130,10 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
|
||||
sourceEvent.getSource().getClass() + "]" );
|
||||
}
|
||||
}
|
||||
|
||||
public int getBandwidth()
|
||||
{
|
||||
return mFrequencyController.getBandwidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the center frequency of the local oscillator.
|
||||
*
|
||||
* @param frequency in hertz
|
||||
* @throws SourceException - if the tuner has any issues
|
||||
*/
|
||||
public void setFrequency(long frequency) throws SourceException
|
||||
{
|
||||
mFrequencyController.setFrequency(frequency);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the center frequency of the local oscillator
|
||||
*
|
||||
* @return frequency in hertz
|
||||
*/
|
||||
public long getFrequency()
|
||||
{
|
||||
return mFrequencyController.getFrequency();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canTune(long frequency)
|
||||
{
|
||||
return mFrequencyController.canTune(frequency);
|
||||
}
|
||||
|
||||
public double getSampleRate()
|
||||
{
|
||||
return mFrequencyController.getSampleRate();
|
||||
}
|
||||
|
||||
public double getFrequencyCorrection()
|
||||
{
|
||||
return mFrequencyController.getFrequencyCorrection();
|
||||
}
|
||||
|
||||
public void setFrequencyCorrection(double correction) throws SourceException
|
||||
{
|
||||
mFrequencyController.setFrequencyCorrection(correction);
|
||||
}
|
||||
|
||||
public long getMinFrequency()
|
||||
{
|
||||
return mFrequencyController.getMinimumFrequency();
|
||||
}
|
||||
|
||||
public long getMaxFrequency()
|
||||
{
|
||||
return mFrequencyController.getMaximumFrequency();
|
||||
}
|
||||
|
||||
private long getMinTunedFrequency() throws SourceException
|
||||
{
|
||||
return mFrequencyController.getFrequency() - (getUsableBandwidth() / 2);
|
||||
}
|
||||
|
||||
private long getMaxTunedFrequency() throws SourceException
|
||||
{
|
||||
return mFrequencyController.getFrequency() + (getUsableBandwidth() / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if channel along with all of the other currently sourced
|
||||
* channels can fit within the tunable bandwidth.
|
||||
*/
|
||||
private boolean canTune(TunerChannel channel)
|
||||
{
|
||||
//Make sure we're within the tunable frequency range of this tuner
|
||||
if(getMinFrequency() < channel.getMinFrequency() && getMaxFrequency() > channel.getMaxFrequency())
|
||||
{
|
||||
//If this is the first lock, then we're good
|
||||
if(mTunedChannels.isEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
int usableBandwidth = getUsableBandwidth();
|
||||
long minLockedFrequency = mTunedChannels.first().getMinFrequency();
|
||||
long maxLockedFrequency = mTunedChannels.last().getMaxFrequency();
|
||||
|
||||
//Requested channel is within current locked channel frequency range
|
||||
if(minLockedFrequency <= channel.getMinFrequency() && channel.getMaxFrequency() <= maxLockedFrequency)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//Requested channel is higher than min locked frequency
|
||||
if(channel.getMaxFrequency() > minLockedFrequency &&
|
||||
channel.getMaxFrequency() - minLockedFrequency <= usableBandwidth)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//Requested channel is lower than the max locked frequency
|
||||
if(channel.getMinFrequency() <= maxLockedFrequency &&
|
||||
maxLockedFrequency - channel.getMinFrequency() <= usableBandwidth)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a digital drop channel (DDC) as a tuner channel from from the tuner, or returns null if the channel
|
||||
* cannot be sourced from the tuner.
|
||||
*
|
||||
* This controller can't provide a channel if the channel, along with the current set of sourced channels, can't be
|
||||
* accomodated within the current tuner bandwidth, or if a center frequency cannot be calculated that will ensure
|
||||
* all of the channels can be accomodated and any defined central DC spike avoided.
|
||||
*
|
||||
* @param tuner to source the channel from
|
||||
* @param channel with defined center frequency and bandwidth
|
||||
* @return fully constructed tuner channel or null
|
||||
* @throws RejectedExecutionException if the decimation processor has an error
|
||||
*/
|
||||
public TunerChannelSource getChannel(Tuner tuner, TunerChannel channel) throws RejectedExecutionException
|
||||
{
|
||||
TunerChannelSource source = null;
|
||||
|
||||
if(canTune(channel))
|
||||
{
|
||||
try
|
||||
{
|
||||
mTunedChannels.add(channel);
|
||||
|
||||
if(requiresLOUpdate(channel))
|
||||
{
|
||||
updateLOFrequency();
|
||||
}
|
||||
|
||||
source = new CICTunerChannelSource(tuner, channel);
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
mTunedChannels.remove(channel);
|
||||
source = null;
|
||||
}
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
public int getChannelCount()
|
||||
{
|
||||
return mTunedChannels.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the tuner's LO frequency must be updated in order to accommodate the tuner channel
|
||||
*/
|
||||
private boolean requiresLOUpdate(TunerChannel channel) throws SourceException
|
||||
{
|
||||
boolean isBelow = channel.getMinFrequency() < getMinTunedFrequency();
|
||||
boolean isAbove = channel.getMaxFrequency() > getMaxTunedFrequency();
|
||||
boolean overlapsCenter = mMiddleUnusable > 0 &&
|
||||
channel.overlaps(getFrequency() - mMiddleUnusable, getFrequency() + mMiddleUnusable);
|
||||
|
||||
return isAbove || isBelow || overlapsCenter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the currently sourced tuner channel from this tuner and shuts down the tuner if no other sources exist.
|
||||
*/
|
||||
public void releaseChannel(TunerChannelSource tunerChannelSource)
|
||||
{
|
||||
if(tunerChannelSource != null)
|
||||
{
|
||||
mTunedChannels.remove(tunerChannelSource.getTunerChannel());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Local Oscillator frequency to accomodate the current set of tuned channels.
|
||||
*
|
||||
* If there is only a single tuned channel, it is placed immediately to the right of the usable bandwidth right
|
||||
* of the central DC spike.
|
||||
*
|
||||
* Otherwise, it places the highest channel frequency at the upper end of the tuner bandwidth, and then iteratively
|
||||
* moves the center frequency higher until all channels fit within the bandwidth and none of the channels overlap
|
||||
* any defined central DC spike unusable region. If a center tune frequency cannot be calculated, throw an
|
||||
* exception so that the most recently added channel can be removed.
|
||||
*
|
||||
* Note: the tuned frequency is not changed until a legitimate new frequency can be calculated. If an exception
|
||||
* is thrown, the current frequency is retained, so that the recently added channel can be removed and all other
|
||||
* channels can continue as previously arranged.
|
||||
*
|
||||
* @throws SourceException if the set of tuner channels, including a recently added channel, cannot be tuned
|
||||
* within the current tuner bandwidth and any central DC spike unusable region.
|
||||
*/
|
||||
private void updateLOFrequency() throws SourceException
|
||||
{
|
||||
long candidateFrequency;
|
||||
|
||||
boolean isValidCandidateFrequency = true;
|
||||
|
||||
//If there is only 1 channel set the center frequency so that the channel is positioned to the right of center
|
||||
if(mTunedChannels.size() == 1)
|
||||
{
|
||||
candidateFrequency = mTunedChannels.first().getMinFrequency() - mMiddleUnusable + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
long minLockedFrequency = mTunedChannels.first().getMinFrequency();
|
||||
long maxLockedFrequency = mTunedChannels.last().getMaxFrequency();
|
||||
|
||||
//Start by placing the highest frequency channel at the high end of the spectrum
|
||||
candidateFrequency = maxLockedFrequency - (getUsableBandwidth() / 2);
|
||||
|
||||
//Iterate the channels and make sure that none of them overlap the center DC spike buffer, if one exists
|
||||
if(mMiddleUnusable > 0)
|
||||
{
|
||||
boolean processingRequired = true;
|
||||
|
||||
while(isValidCandidateFrequency && processingRequired)
|
||||
{
|
||||
processingRequired = false;
|
||||
|
||||
long minAvoid = candidateFrequency - mMiddleUnusable;
|
||||
long maxAvoid = candidateFrequency + mMiddleUnusable;
|
||||
|
||||
//If any of the center channel(s) overlap the central DC spike avoid area, we'll iteratively
|
||||
//increase the tuned frequency causing the set of channels to move left in the tuned bandwidth until
|
||||
//we either find a good center tune frequency, or we walk the lowest frequency channel out of the
|
||||
//minimum tuned range, in which case we'll throw an exception indicating we don't have a solution.
|
||||
for(TunerChannel channel : mTunedChannels)
|
||||
{
|
||||
if(channel.overlaps(minAvoid, maxAvoid))
|
||||
{
|
||||
//Calculate a tuned frequency adjustment that places this overlapping channel just to the
|
||||
//left of the central DC spike avoid zone
|
||||
long adjustment = channel.getMaxFrequency() - minAvoid + 1;
|
||||
|
||||
//If the candidate frequency doesn't push the lowest channel out of bounds, make adjustment
|
||||
if(candidateFrequency + adjustment - (getUsableBandwidth() / 2) <= minLockedFrequency)
|
||||
{
|
||||
candidateFrequency += adjustment;
|
||||
processingRequired = true;
|
||||
}
|
||||
//Otherwise, punt and indicate that we can't find a center frequency or add the channel
|
||||
else
|
||||
{
|
||||
isValidCandidateFrequency = false;
|
||||
}
|
||||
|
||||
//break out of the for/each loop, so that we can start over again with all of the channels
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(isValidCandidateFrequency)
|
||||
{
|
||||
mFrequencyController.setFrequency(candidateFrequency);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new SourceException("Couldn't calculate viable center frequency from set of tuner channels");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Usable bandwidth - total bandwidth minus the unusable space at either end of the spectrum.
|
||||
*/
|
||||
private int getUsableBandwidth()
|
||||
{
|
||||
return (int)(getBandwidth() * mUsableBandwidthPercentage);
|
||||
return getFrequencyController().canTune(frequency);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -264,7 +264,7 @@ public class TunerEditor extends Editor<Tuner>
|
||||
|
||||
//Set the displayed frequency without adjusting the tuner's frequency
|
||||
mFrequencyControl.setFrequency(
|
||||
tuner.getTunerController().getFrequency(), false );
|
||||
tuner.getTunerController().getFrequencyController().getFrequency(), false );
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -220,13 +220,13 @@ public class TunerModel extends AbstractTableModel implements Listener<TunerEven
|
||||
case TUNER_ID:
|
||||
return tuner.getUniqueID();
|
||||
case SAMPLE_RATE:
|
||||
double sampleRate = tuner.getTunerController().getSampleRate();
|
||||
double sampleRate = tuner.getTunerController().getFrequencyController().getSampleRate();
|
||||
|
||||
return mSampleRateFormat.format(sampleRate / 1E6D) + MHZ;
|
||||
case FREQUENCY:
|
||||
try
|
||||
{
|
||||
long frequency = tuner.getTunerController().getFrequency();
|
||||
long frequency = tuner.getTunerController().getFrequencyController().getFrequency();
|
||||
|
||||
return mFrequencyFormat.format(frequency / 1E6D) + MHZ;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import org.usb4java.DeviceHandle;
|
||||
import org.usb4java.LibUsb;
|
||||
import org.usb4java.LibUsbException;
|
||||
import source.SourceException;
|
||||
import source.tuner.TunerController;
|
||||
import source.tuner.configuration.TunerConfiguration;
|
||||
import source.tuner.usb.USBTransferProcessor;
|
||||
import source.tuner.usb.USBTunerController;
|
||||
@@ -84,10 +83,10 @@ public class AirspyTunerController extends USBTunerController
|
||||
new AirspySampleRate(0, 10000000, "10.00 MHz");
|
||||
|
||||
private static final long USB_TIMEOUT_MS = 2000l; //milliseconds
|
||||
private static final byte USB_INTERFACE = (byte) 0x0;
|
||||
private static final byte USB_INTERFACE = (byte)0x0;
|
||||
private static final int USB_TRANSFER_BUFFER_SIZE = 262144;
|
||||
private static final byte USB_REQUEST_IN = (byte) (LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE);
|
||||
private static final byte USB_REQUEST_OUT = (byte) (LibUsb.ENDPOINT_OUT | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE);
|
||||
private static final byte USB_REQUEST_IN = (byte)(LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE);
|
||||
private static final byte USB_REQUEST_OUT = (byte)(LibUsb.ENDPOINT_OUT | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE);
|
||||
public static final DecimalFormat MHZ_FORMATTER = new DecimalFormat("#.00 MHz");
|
||||
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AirspyTunerController.class);
|
||||
@@ -156,7 +155,7 @@ public class AirspyTunerController extends USBTunerController
|
||||
mLog.error("Couldn't enable airspy receiver mode", e);
|
||||
}
|
||||
|
||||
setFrequency(FREQUENCY_DEFAULT);
|
||||
getFrequencyController().setFrequency(FREQUENCY_DEFAULT);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -269,7 +268,7 @@ public class AirspyTunerController extends USBTunerController
|
||||
{
|
||||
if(config instanceof AirspyTunerConfiguration)
|
||||
{
|
||||
AirspyTunerConfiguration airspy = (AirspyTunerConfiguration) config;
|
||||
AirspyTunerConfiguration airspy = (AirspyTunerConfiguration)config;
|
||||
|
||||
int sampleRate = airspy.getSampleRate();
|
||||
|
||||
@@ -311,7 +310,7 @@ public class AirspyTunerController extends USBTunerController
|
||||
//the custom values.
|
||||
setGain(airspy.getGain());
|
||||
|
||||
setFrequencyCorrection(airspy.getFrequencyCorrection());
|
||||
getFrequencyController().setFrequencyCorrection(airspy.getFrequencyCorrection());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@@ -321,7 +320,7 @@ public class AirspyTunerController extends USBTunerController
|
||||
|
||||
try
|
||||
{
|
||||
setFrequency(airspy.getFrequency());
|
||||
getFrequencyController().setFrequency(airspy.getFrequency());
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
@@ -339,7 +338,7 @@ public class AirspyTunerController extends USBTunerController
|
||||
@Override
|
||||
public long getTunedFrequency() throws SourceException
|
||||
{
|
||||
return mFrequencyController.getTunedFrequency();
|
||||
return getFrequencyController().getTunedFrequency();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -351,7 +350,7 @@ public class AirspyTunerController extends USBTunerController
|
||||
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
buffer.putInt((int) frequency);
|
||||
buffer.putInt((int)frequency);
|
||||
|
||||
buffer.rewind();
|
||||
|
||||
@@ -390,8 +389,7 @@ public class AirspyTunerController extends USBTunerController
|
||||
* is not supported by the current firmware
|
||||
* @throws UsbException if there was a USB error
|
||||
*/
|
||||
public void setSampleRate(AirspySampleRate rate) throws
|
||||
LibUsbException, UsbException, SourceException
|
||||
public void setSampleRate(AirspySampleRate rate) throws LibUsbException, UsbException, SourceException
|
||||
{
|
||||
if(rate.getRate() != mSampleRate)
|
||||
{
|
||||
@@ -405,7 +403,7 @@ public class AirspyTunerController extends USBTunerController
|
||||
else
|
||||
{
|
||||
mSampleRate = rate.getRate();
|
||||
mFrequencyController.setSampleRate(mSampleRate);
|
||||
getFrequencyController().setSampleRate(mSampleRate);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -463,7 +461,7 @@ public class AirspyTunerController extends USBTunerController
|
||||
throw new UsbException("Couldnt set sample packing enabled: " + enabled);
|
||||
}
|
||||
|
||||
/* If we didn't throw an exception above, then update the sample adapter
|
||||
/* If we didn't throw an exception above, then update the sample adapter
|
||||
* to process samples accordingly */
|
||||
mSampleAdapter.setSamplePacking(enabled);
|
||||
}
|
||||
@@ -651,7 +649,7 @@ public class AirspyTunerController extends USBTunerController
|
||||
*/
|
||||
private static String formatSampleRate(int rate)
|
||||
{
|
||||
return MHZ_FORMATTER.format((double) rate / 1E6d);
|
||||
return MHZ_FORMATTER.format((double)rate / 1E6d);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -678,7 +676,7 @@ public class AirspyTunerController extends USBTunerController
|
||||
mDeviceInfo = new AirspyDeviceInformation();
|
||||
}
|
||||
|
||||
/* Board ID */
|
||||
/* Board ID */
|
||||
try
|
||||
{
|
||||
int boardID = readByte(Command.BOARD_ID_READ, 0, 0, true);
|
||||
@@ -690,7 +688,7 @@ public class AirspyTunerController extends USBTunerController
|
||||
mLog.error("Error reading airspy board ID", e);
|
||||
}
|
||||
|
||||
/* Version String */
|
||||
/* Version String */
|
||||
try
|
||||
{
|
||||
//NOTE: libairspy is internally reading 127 bytes, however airspy_info
|
||||
@@ -703,8 +701,8 @@ public class AirspyTunerController extends USBTunerController
|
||||
{
|
||||
mLog.error("Error reading airspy version string", e);
|
||||
}
|
||||
|
||||
/* Part ID and Serial Number */
|
||||
|
||||
/* Part ID and Serial Number */
|
||||
try
|
||||
{
|
||||
//Read 6 x 32-bit integers = 24 bytes
|
||||
@@ -739,8 +737,8 @@ public class AirspyTunerController extends USBTunerController
|
||||
int transferred = LibUsb.controlTransfer(mDeviceHandle,
|
||||
USB_REQUEST_IN,
|
||||
command.getValue(),
|
||||
(short) value,
|
||||
(short) index,
|
||||
(short)value,
|
||||
(short)index,
|
||||
buffer,
|
||||
USB_TIMEOUT_MS);
|
||||
|
||||
@@ -789,8 +787,8 @@ public class AirspyTunerController extends USBTunerController
|
||||
int transferred = LibUsb.controlTransfer(mDeviceHandle,
|
||||
USB_REQUEST_IN,
|
||||
command.getValue(),
|
||||
(short) value,
|
||||
(short) index,
|
||||
(short)value,
|
||||
(short)index,
|
||||
buffer,
|
||||
USB_TIMEOUT_MS);
|
||||
|
||||
@@ -829,8 +827,8 @@ public class AirspyTunerController extends USBTunerController
|
||||
int transferred = LibUsb.controlTransfer(mDeviceHandle,
|
||||
USB_REQUEST_OUT,
|
||||
command.getValue(),
|
||||
(short) value,
|
||||
(short) index,
|
||||
(short)value,
|
||||
(short)index,
|
||||
buffer,
|
||||
USB_TIMEOUT_MS);
|
||||
|
||||
@@ -1081,7 +1079,7 @@ public class AirspyTunerController extends USBTunerController
|
||||
|
||||
public byte getValue()
|
||||
{
|
||||
return (byte) mValue;
|
||||
return (byte)mValue;
|
||||
}
|
||||
|
||||
public static Command fromValue(int value)
|
||||
|
||||
@@ -208,7 +208,7 @@ public class AirspyTunerEditor extends TunerConfigurationEditor
|
||||
|
||||
try
|
||||
{
|
||||
mController.setFrequencyCorrection( value );
|
||||
mController.getFrequencyController().setFrequencyCorrection( value );
|
||||
}
|
||||
catch ( SourceException e1 )
|
||||
{
|
||||
|
||||
337
src/source/tuner/channel/ChannelManager.java
Normal file
337
src/source/tuner/channel/ChannelManager.java
Normal file
@@ -0,0 +1,337 @@
|
||||
/*******************************************************************************
|
||||
* 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 source.tuner.channel;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sample.Listener;
|
||||
import source.Source;
|
||||
import source.SourceEvent;
|
||||
import source.SourceException;
|
||||
import source.tuner.Tuner;
|
||||
import source.tuner.channel.cic.CICTunerChannelSource;
|
||||
|
||||
import java.util.SortedSet;
|
||||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
public abstract class ChannelManager implements Listener<SourceEvent>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelManager.class);
|
||||
|
||||
private FrequencyController mFrequencyController;
|
||||
private SortedSet<TunerChannel> mTunedChannels = new ConcurrentSkipListSet<>();
|
||||
|
||||
/**
|
||||
* Channel manager provides access to Digital Drop Channel (DDC) tuner channel sources and automatic updating of
|
||||
* a frequency controller to ensure that the center tuned frequency is correct for the provided DDC channels.
|
||||
*
|
||||
* @param frequencyController with a center tuned frequency that can be adjusted
|
||||
*/
|
||||
public ChannelManager(FrequencyController frequencyController)
|
||||
{
|
||||
mFrequencyController = frequencyController;
|
||||
}
|
||||
|
||||
protected FrequencyController getFrequencyController()
|
||||
{
|
||||
return mFrequencyController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a tuner channel source for the tuner channel if possible.
|
||||
*
|
||||
* @param tunerChannel to source
|
||||
* @return tuner channel source or null if the tuner channel cannot be sourced by this channel manager
|
||||
*/
|
||||
public TunerChannelSource getSource(TunerChannel tunerChannel)
|
||||
{
|
||||
TunerChannelSource tunerChannelSource = null;
|
||||
|
||||
if(canTune(tunerChannel))
|
||||
{
|
||||
//Add the channel so that we can calculate the new center frequency
|
||||
mTunedChannels.add(tunerChannel);
|
||||
|
||||
long centerFrequency = calculateCenterFrequency();
|
||||
|
||||
if(centerFrequency != getFrequencyController().getFrequency())
|
||||
{
|
||||
try
|
||||
{
|
||||
getFrequencyController().setLockedFrequency(centerFrequency);
|
||||
tunerChannelSource = getTunerChannelSource(tunerChannel);
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
mLog.error("Error setting tuner frequency while creating new tuner channel source for channel: " +
|
||||
tunerChannel, se);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//If we're not successful, remove the tuner channel from the sorted set
|
||||
if(tunerChannelSource == null && mTunedChannels.contains(tunerChannel))
|
||||
{
|
||||
mTunedChannels.remove(tunerChannel);
|
||||
}
|
||||
|
||||
return tunerChannelSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the source and performs any cleanup.
|
||||
* @param source to release that was previously provided by this channel manager
|
||||
*/
|
||||
public void releaseSource(TunerChannelSource source)
|
||||
{
|
||||
mTunedChannels.remove(source.getTunerChannel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the center frequency to use for the current set of tuner channels.
|
||||
*/
|
||||
protected abstract long calculateCenterFrequency();
|
||||
|
||||
/**
|
||||
* Constructs the actual tuner channel source for the tuner channel. This method assumes that the tuner channel
|
||||
* can be tuned and that the tuner is correctly center tuned to source the channel.
|
||||
* @param tunerChannel to source
|
||||
* @return tuner channel source or null if there are any errors in creating the tuner channel source
|
||||
*/
|
||||
protected abstract TunerChannelSource getTunerChannelSource(TunerChannel tunerChannel);
|
||||
|
||||
/**
|
||||
* Indicates if channel along with all of the other currently sourced channels can fit within the tunable bandwidth.
|
||||
*/
|
||||
private boolean canTune(TunerChannel channel)
|
||||
{
|
||||
//Make sure we're within the tunable frequency range of this tuner
|
||||
if(getFrequencyController().getMinimumFrequency() < channel.getMinFrequency() &&
|
||||
getFrequencyController().getMaximumFrequency() > channel.getMaxFrequency())
|
||||
{
|
||||
//If this is the first lock, then we're good
|
||||
if(mTunedChannels.isEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
int usableBandwidth = getFrequencyController().getUsableBandwidth();
|
||||
long minLockedFrequency = mTunedChannels.first().getMinFrequency();
|
||||
long maxLockedFrequency = mTunedChannels.last().getMaxFrequency();
|
||||
|
||||
//Requested channel is within current locked channel frequency range
|
||||
if(minLockedFrequency <= channel.getMinFrequency() && channel.getMaxFrequency() <= maxLockedFrequency)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//Requested channel is higher than min locked frequency
|
||||
if(channel.getMaxFrequency() > minLockedFrequency &&
|
||||
channel.getMaxFrequency() - minLockedFrequency <= usableBandwidth)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//Requested channel is lower than the max locked frequency
|
||||
if(channel.getMinFrequency() <= maxLockedFrequency &&
|
||||
maxLockedFrequency - channel.getMinFrequency() <= usableBandwidth)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a digital drop channel (DDC) as a tuner channel from from the tuner, or returns null if the channel
|
||||
* cannot be sourced from the tuner.
|
||||
*
|
||||
* This controller can't provide a channel if the channel, along with the current set of sourced channels, can't be
|
||||
* accomodated within the current tuner bandwidth, or if a center frequency cannot be calculated that will ensure
|
||||
* all of the channels can be accomodated and any defined central DC spike avoided.
|
||||
*
|
||||
* @param tuner to source the channel from
|
||||
* @param channel with defined center frequency and bandwidth
|
||||
* @return fully constructed tuner channel or null
|
||||
* @throws RejectedExecutionException if the decimation processor has an error
|
||||
*/
|
||||
public TunerChannelSource getChannel(Tuner tuner, TunerChannel channel) throws RejectedExecutionException
|
||||
{
|
||||
TunerChannelSource source = null;
|
||||
|
||||
if(canTune(channel))
|
||||
{
|
||||
try
|
||||
{
|
||||
mTunedChannels.add(channel);
|
||||
|
||||
if(requiresLOUpdate(channel))
|
||||
{
|
||||
updateLOFrequency();
|
||||
}
|
||||
|
||||
source = new CICTunerChannelSource(tuner, channel);
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
mTunedChannels.remove(channel);
|
||||
source = null;
|
||||
}
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
public int getChannelCount()
|
||||
{
|
||||
return mTunedChannels.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the tuner's LO frequency must be updated in order to accommodate the tuner channel
|
||||
*/
|
||||
private boolean requiresLOUpdate(TunerChannel channel) throws SourceException
|
||||
{
|
||||
int usableBandwidth = getFrequencyController().getUsableBandwidth();
|
||||
|
||||
long minimumTunedFrequency = getFrequencyController().getFrequency() - (usableBandwidth / 2);
|
||||
boolean isBelow = channel.getMinFrequency() < minimumTunedFrequency;
|
||||
|
||||
long maximumTunedFrequency = getFrequencyController().getFrequency() + (usableBandwidth / 2);
|
||||
boolean isAbove = channel.getMaxFrequency() > maximumTunedFrequency;
|
||||
|
||||
long centerBlackoutBandwidth = getFrequencyController().getCenterBlackoutBandwidth();
|
||||
boolean overlapsCenter = centerBlackoutBandwidth > 0 &&
|
||||
channel.overlaps(getFrequencyController().getFrequency() - centerBlackoutBandwidth,
|
||||
getFrequencyController().getFrequency() + centerBlackoutBandwidth);
|
||||
|
||||
return isAbove || isBelow || overlapsCenter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the currently sourced tuner channel from this tuner and shuts down the tuner if no other sources exist.
|
||||
*/
|
||||
public void releaseChannel(TunerChannelSource tunerChannelSource)
|
||||
{
|
||||
if(tunerChannelSource != null)
|
||||
{
|
||||
mTunedChannels.remove(tunerChannelSource.getTunerChannel());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Local Oscillator frequency to accomodate the current set of tuned channels.
|
||||
*
|
||||
* If there is only a single tuned channel, it is placed immediately to the right of the usable bandwidth right
|
||||
* of the central DC spike.
|
||||
*
|
||||
* Otherwise, it places the highest channel frequency at the upper end of the tuner bandwidth, and then iteratively
|
||||
* moves the center frequency higher until all channels fit within the bandwidth and none of the channels overlap
|
||||
* any defined central DC spike unusable region. If a center tune frequency cannot be calculated, throw an
|
||||
* exception so that the most recently added channel can be removed.
|
||||
*
|
||||
* Note: the tuned frequency is not changed until a legitimate new frequency can be calculated. If an exception
|
||||
* is thrown, the current frequency is retained, so that the recently added channel can be removed and all other
|
||||
* channels can continue as previously arranged.
|
||||
*
|
||||
* @throws SourceException if the set of tuner channels, including a recently added channel, cannot be tuned
|
||||
* within the current tuner bandwidth and any central DC spike unusable region.
|
||||
*/
|
||||
private void updateLOFrequency() throws SourceException
|
||||
{
|
||||
long candidateFrequency;
|
||||
|
||||
boolean isValidCandidateFrequency = true;
|
||||
|
||||
//If there is only 1 channel set the center frequency so that the channel is positioned to the right of center
|
||||
if(mTunedChannels.size() == 1)
|
||||
{
|
||||
candidateFrequency = mTunedChannels.first().getMinFrequency() -
|
||||
getFrequencyController().getCenterBlackoutBandwidth() + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
long minLockedFrequency = mTunedChannels.first().getMinFrequency();
|
||||
long maxLockedFrequency = mTunedChannels.last().getMaxFrequency();
|
||||
|
||||
//Start by placing the highest frequency channel at the high end of the spectrum
|
||||
candidateFrequency = maxLockedFrequency - (getFrequencyController().getUsableBandwidth() / 2);
|
||||
|
||||
//Iterate the channels and make sure that none of them overlap the center DC spike buffer, if one exists
|
||||
if(getFrequencyController().getCenterBlackoutBandwidth() > 0)
|
||||
{
|
||||
boolean processingRequired = true;
|
||||
|
||||
while(isValidCandidateFrequency && processingRequired)
|
||||
{
|
||||
processingRequired = false;
|
||||
|
||||
long centerBlackout = getFrequencyController().getCenterBlackoutBandwidth();
|
||||
|
||||
long minAvoid = candidateFrequency - centerBlackout;
|
||||
long maxAvoid = candidateFrequency + centerBlackout;
|
||||
|
||||
//If any of the center channel(s) overlap the central DC spike avoid area, we'll iteratively
|
||||
//increase the tuned frequency causing the set of channels to move left in the tuned bandwidth until
|
||||
//we either find a good center tune frequency, or we walk the lowest frequency channel out of the
|
||||
//minimum tuned range, in which case we'll throw an exception indicating we don't have a solution.
|
||||
for(TunerChannel channel : mTunedChannels)
|
||||
{
|
||||
if(channel.overlaps(minAvoid, maxAvoid))
|
||||
{
|
||||
//Calculate a tuned frequency adjustment that places this overlapping channel just to the
|
||||
//left of the central DC spike avoid zone
|
||||
long adjustment = channel.getMaxFrequency() - minAvoid + 1;
|
||||
|
||||
//If the candidate frequency doesn't push the lowest channel out of bounds, make adjustment
|
||||
if(candidateFrequency + adjustment -
|
||||
(getFrequencyController().getUsableBandwidth() / 2) <= minLockedFrequency)
|
||||
{
|
||||
candidateFrequency += adjustment;
|
||||
processingRequired = true;
|
||||
}
|
||||
//Otherwise, punt and indicate that we can't find a center frequency or add the channel
|
||||
else
|
||||
{
|
||||
isValidCandidateFrequency = false;
|
||||
}
|
||||
|
||||
//break out of the for/each loop, so that we can start over again with all of the channels
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(isValidCandidateFrequency)
|
||||
{
|
||||
mFrequencyController.setLockedFrequency(candidateFrequency);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new SourceException("Couldn't calculate viable center frequency from set of tuner channels");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package source.tuner.frequency;
|
||||
package source.tuner.channel;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -26,6 +26,7 @@ import source.SourceEvent;
|
||||
import source.SourceException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class FrequencyController
|
||||
{
|
||||
@@ -38,37 +39,59 @@ public class FrequencyController
|
||||
private long mMaximumFrequency;
|
||||
private double mFrequencyCorrection = 0.0d;
|
||||
private double mSampleRate = 0.0d;
|
||||
private long mCenterBlackoutBandwidth;
|
||||
private double mUsableBandwidthPercentage;
|
||||
private boolean mLocked;
|
||||
|
||||
private ArrayList<ISourceEventProcessor> mProcessors = new ArrayList<>();
|
||||
private List<ISourceEventProcessor> mProcessors = new ArrayList<>();
|
||||
|
||||
public FrequencyController(Tunable tunable, long minFrequency, long maxFrequency, double frequencyCorrection)
|
||||
public FrequencyController(Tunable tunable, long minFrequency, long maxFrequency, double frequencyCorrection,
|
||||
long centerBlackoutBandwidth, double usableBandwidthPercentage)
|
||||
{
|
||||
mTunable = tunable;
|
||||
mMinimumFrequency = minFrequency;
|
||||
mMaximumFrequency = maxFrequency;
|
||||
mFrequencyCorrection = frequencyCorrection;
|
||||
mCenterBlackoutBandwidth = centerBlackoutBandwidth;
|
||||
mUsableBandwidthPercentage = usableBandwidthPercentage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bandwidth in hertz
|
||||
*/
|
||||
public int getBandwidth()
|
||||
public boolean isLocked()
|
||||
{
|
||||
return (int)mSampleRate;
|
||||
return mLocked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set sample rate in hertz
|
||||
* Sets the locked status. When this control is locked, the sample rate cannot be changed and the center
|
||||
* tuned frequency can only be changed via the setLockedFrequency() method.
|
||||
*
|
||||
* Note: this method is package-private and is intended to be used (only) by the ChannelManager.
|
||||
*
|
||||
* @param locked
|
||||
*/
|
||||
void setLocked(boolean locked)
|
||||
{
|
||||
mLocked = locked;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set sample rate/bandwidth in hertz
|
||||
*/
|
||||
public void setSampleRate(int sampleRate) throws SourceException
|
||||
{
|
||||
if(isLocked())
|
||||
{
|
||||
throw new FrequencyControllerLockedException();
|
||||
}
|
||||
|
||||
mSampleRate = sampleRate;
|
||||
|
||||
broadcastSampleRateChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sample rate in hertz
|
||||
* Get sample rate/bandwidth in hertz
|
||||
*/
|
||||
public double getSampleRate()
|
||||
{
|
||||
@@ -91,6 +114,23 @@ public class FrequencyController
|
||||
* version of the requested frequency
|
||||
*/
|
||||
public void setFrequency(long frequency) throws SourceException
|
||||
{
|
||||
if(isLocked())
|
||||
{
|
||||
throw new FrequencyControllerLockedException();
|
||||
}
|
||||
|
||||
setFrequency(frequency, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the frequency when the frequency controller is locked. Note: this method is package-private and is
|
||||
* only intended to be used by the ChannelManager.
|
||||
*
|
||||
* @param frequency to set as the center tuned frequency
|
||||
* @throws SourceException if there is an error while setting the frequency
|
||||
*/
|
||||
void setLockedFrequency(long frequency) throws SourceException
|
||||
{
|
||||
setFrequency(frequency, true);
|
||||
}
|
||||
@@ -250,6 +290,34 @@ public class FrequencyController
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies the bandwidth of a blackout frequency range centered about the center frequency. This is
|
||||
* used to block the frequency range where a DC spike may exist.
|
||||
* @return center blackout bandwidth in hertz
|
||||
*/
|
||||
public long getCenterBlackoutBandwidth()
|
||||
{
|
||||
return mCenterBlackoutBandwidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Percentage of the available bandwidth that is usable. Sources with filter roll-off at the ends of the
|
||||
* spectral range can limit the usable bandwidth to avoid providing samples at these spectral ends.
|
||||
*
|
||||
* @return bandwidth percentage (0.0 <> 1.0)
|
||||
*/
|
||||
public double getUsableBandwidthPercentage()
|
||||
{
|
||||
return mUsableBandwidthPercentage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Usable bandwidth in hertz calculated as the sample rate constrained by the usable bandwidth percentage.
|
||||
*/
|
||||
public int getUsableBandwidth()
|
||||
{
|
||||
return (int)(getSampleRate() * getUsableBandwidthPercentage());
|
||||
}
|
||||
|
||||
public interface Tunable
|
||||
{
|
||||
@@ -18,28 +18,18 @@
|
||||
******************************************************************************/
|
||||
package source.tuner.channel;
|
||||
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
import source.SourceException;
|
||||
|
||||
public interface TunerChannelProvider
|
||||
public class FrequencyControllerLockedException extends SourceException
|
||||
{
|
||||
/**
|
||||
* Returns a tuner frequency channel source, tuned to the correct frequency
|
||||
*
|
||||
* @param frequency - desired frequency
|
||||
*
|
||||
* @return - source for 48k sample rate
|
||||
*/
|
||||
public abstract TunerChannelSource getChannel( TunerChannel channel )
|
||||
throws RejectedExecutionException, SourceException;
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Releases the tuned channel resources
|
||||
*
|
||||
* @param channel - previously obtained tuner channel
|
||||
*/
|
||||
public abstract void releaseChannel( TunerChannelSource source );
|
||||
|
||||
|
||||
/**
|
||||
* Indicates an attempt to adjust frequency or sample rate on a locked frequency controller.
|
||||
*/
|
||||
public FrequencyControllerLockedException()
|
||||
{
|
||||
super( "Frequency controller is locked -- frequency and sample rate cannot be changed until the controller" +
|
||||
" is unlocked by the channel manager" );
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
|
||||
private Listener<SourceEvent> mProducerSourceEventListener;
|
||||
private Listener<SourceEvent> mConsumerSourceEventListener;
|
||||
private BufferProcessor mBufferProcessor = new BufferProcessor();
|
||||
private boolean mExpended = false;
|
||||
|
||||
/**
|
||||
* Tuner Channel Source is a Digital Drop Channel (DDC) abstract class that defines the minimum functionality
|
||||
@@ -116,6 +117,12 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
|
||||
*/
|
||||
public void start()
|
||||
{
|
||||
if(mExpended)
|
||||
{
|
||||
throw new IllegalStateException("Cannot start an expended tuner channel source. You must request a new" +
|
||||
" tuner channel source each time. Tuner channel sources are one-use only");
|
||||
}
|
||||
|
||||
//Broadcast current frequency and sample rate so consumer can configure correctly
|
||||
broadcastConsumerSourceEvent(SourceEvent.frequencyChange(getFrequency()));
|
||||
broadcastConsumerSourceEvent(SourceEvent.sampleRateChange(getSampleRate()));
|
||||
@@ -134,6 +141,8 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
mExpended = true;
|
||||
|
||||
if(mProducerSourceEventListener != null)
|
||||
{
|
||||
mProducerSourceEventListener.receive(SourceEvent.stopSampleStream(this));
|
||||
@@ -146,7 +155,11 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
//Reset is not valid for a tuner channel source - ignored
|
||||
if(mExpended)
|
||||
{
|
||||
throw new IllegalStateException("Cannot reset an expended tuner channel source. You must request a new" +
|
||||
" tuner channel source each time. Tuner channel sources are one-use only");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package source.tuner.channel;
|
||||
package source.tuner.channel.cic;
|
||||
|
||||
import dsp.filter.FilterFactory;
|
||||
import dsp.filter.Window;
|
||||
@@ -31,11 +31,13 @@ import sample.complex.Complex;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import source.SourceEvent;
|
||||
import source.tuner.Tuner;
|
||||
import source.tuner.channel.TunerChannel;
|
||||
import source.tuner.channel.TunerChannelSource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CICTunerChannelSource extends TunerChannelSource
|
||||
public class CICTunerChannelSource extends TunerChannelSource implements Listener<ComplexBuffer>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(CICTunerChannelSource.class);
|
||||
|
||||
@@ -77,12 +79,13 @@ public class CICTunerChannelSource extends TunerChannelSource
|
||||
mBuffer.setSourceOverflowListener(this);
|
||||
|
||||
//Setup the frequency translator to the current source frequency
|
||||
mTunerFrequency = tuner.getTunerController().getFrequency();
|
||||
mTunerFrequency = tuner.getTunerController().getFrequencyController().getFrequency();
|
||||
long frequencyOffset = mTunerFrequency - getTunerChannel().getFrequency();
|
||||
mFrequencyCorrectionMixer = new Oscillator(frequencyOffset, tuner.getTunerController().getSampleRate());
|
||||
mFrequencyCorrectionMixer = new Oscillator(frequencyOffset,
|
||||
tuner.getTunerController().getFrequencyController().getSampleRate());
|
||||
|
||||
//Setup the decimation filter chain
|
||||
setSampleRate(tuner.getTunerController().getSampleRate());
|
||||
setSampleRate(tuner.getTunerController().getFrequencyController().getSampleRate());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -377,7 +377,7 @@ public class TunerConfigurationModel extends AbstractTableModel
|
||||
{
|
||||
TunerConfiguration assigned = getTunerConfiguration( tuner.getTunerType(), tuner.getUniqueID() );
|
||||
|
||||
assigned.setFrequency( tuner.getTunerController().getFrequency() );
|
||||
assigned.setFrequency( tuner.getTunerController().getFrequencyController().getFrequency() );
|
||||
|
||||
broadcast( new TunerConfigurationEvent( assigned, Event.CHANGE ) );
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public class FCD1TunerController extends FCDTunerController
|
||||
{
|
||||
super.init();
|
||||
|
||||
mFrequencyController.setSampleRate(SAMPLE_RATE);
|
||||
getFrequencyController().setSampleRate(SAMPLE_RATE);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -120,7 +120,7 @@ public class FCD1TunerController extends FCDTunerController
|
||||
|
||||
try
|
||||
{
|
||||
setFrequencyCorrection(config.getFrequencyCorrection());
|
||||
getFrequencyController().setFrequencyCorrection(config.getFrequencyCorrection());
|
||||
setLNAGain(config.getLNAGain());
|
||||
setLNAEnhance(config.getLNAEnhance());
|
||||
setMixerGain(config.getMixerGain());
|
||||
@@ -142,7 +142,7 @@ public class FCD1TunerController extends FCDTunerController
|
||||
|
||||
try
|
||||
{
|
||||
setFrequency(config.getFrequency());
|
||||
getFrequencyController().setFrequency(config.getFrequency());
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
|
||||
@@ -149,7 +149,7 @@ public class FCD1TunerEditor extends TunerConfigurationEditor
|
||||
|
||||
try
|
||||
{
|
||||
mController.setFrequencyCorrection( value );
|
||||
mController.getFrequencyController().setFrequencyCorrection( value );
|
||||
save();
|
||||
}
|
||||
catch ( SourceException e1 )
|
||||
|
||||
@@ -52,7 +52,7 @@ public class FCD2TunerController extends FCDTunerController
|
||||
{
|
||||
super.init();
|
||||
|
||||
mFrequencyController.setSampleRate(SAMPLE_RATE);
|
||||
getFrequencyController().setSampleRate(SAMPLE_RATE);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -112,7 +112,7 @@ public class FCD2TunerController extends FCDTunerController
|
||||
{
|
||||
FCD2TunerConfiguration fcd2 = (FCD2TunerConfiguration)config;
|
||||
|
||||
setFrequencyCorrection(fcd2.getFrequencyCorrection());
|
||||
getFrequencyController().setFrequencyCorrection(fcd2.getFrequencyCorrection());
|
||||
|
||||
long frequency = MINIMUM_TUNABLE_FREQUENCY;
|
||||
|
||||
@@ -122,13 +122,13 @@ public class FCD2TunerController extends FCDTunerController
|
||||
frequency = fcd2.getFrequency();
|
||||
}
|
||||
|
||||
setFrequency(frequency);
|
||||
getFrequencyController().setFrequency(frequency);
|
||||
setLNAGain(fcd2.getGainLNA());
|
||||
setMixerGain(fcd2.getGainMixer());
|
||||
|
||||
try
|
||||
{
|
||||
setFrequency(fcd2.getFrequency());
|
||||
getFrequencyController().setFrequency(fcd2.getFrequency());
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
|
||||
@@ -141,7 +141,7 @@ public class FCD2TunerEditor extends TunerConfigurationEditor
|
||||
|
||||
try
|
||||
{
|
||||
mController.setFrequencyCorrection( value );
|
||||
mController.getFrequencyController().setFrequencyCorrection( value );
|
||||
save();
|
||||
}
|
||||
catch ( SourceException e1 )
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
package source.tuner.frequency;
|
||||
|
||||
public class FrequencyLimitException extends Exception
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private long mFrequencyLimit;
|
||||
private long mRequestedFrequency;
|
||||
|
||||
/**
|
||||
* Indicates that the requested frequency is outside of the tunable bounds
|
||||
* of the tuner.
|
||||
* @param message
|
||||
* @param frequency
|
||||
*/
|
||||
public FrequencyLimitException( long requestedFrequency,
|
||||
long frequencyLimit )
|
||||
{
|
||||
super( "Requested frequency [" + requestedFrequency +
|
||||
"] exceeds the limit [" + frequencyLimit + "]. Tuned "
|
||||
+ "frequency was set to the limit value" );
|
||||
|
||||
mRequestedFrequency = requestedFrequency;
|
||||
mFrequencyLimit = frequencyLimit;
|
||||
}
|
||||
|
||||
public long getRequestedFrequency()
|
||||
{
|
||||
return mRequestedFrequency;
|
||||
}
|
||||
|
||||
public long getFrequencyLimit()
|
||||
{
|
||||
return mFrequencyLimit;
|
||||
}
|
||||
}
|
||||
@@ -138,7 +138,7 @@ public class HackRFTunerController extends USBTunerController
|
||||
|
||||
setMode(Mode.RECEIVE);
|
||||
|
||||
setFrequency(DEFAULT_FREQUENCY);
|
||||
getFrequencyController().setFrequency(DEFAULT_FREQUENCY);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@@ -306,7 +306,7 @@ public class HackRFTunerController extends USBTunerController
|
||||
*/
|
||||
public long getTunedFrequency() throws SourceException
|
||||
{
|
||||
return mFrequencyController.getTunedFrequency();
|
||||
return getFrequencyController().getTunedFrequency();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -353,11 +353,11 @@ public class HackRFTunerController extends USBTunerController
|
||||
try
|
||||
{
|
||||
setSampleRate(hackRFConfig.getSampleRate());
|
||||
setFrequencyCorrection(hackRFConfig.getFrequencyCorrection());
|
||||
getFrequencyController().setFrequencyCorrection(hackRFConfig.getFrequencyCorrection());
|
||||
setAmplifierEnabled(hackRFConfig.getAmplifierEnabled());
|
||||
setLNAGain(hackRFConfig.getLNAGain());
|
||||
setVGAGain(hackRFConfig.getVGAGain());
|
||||
setFrequency(getFrequency());
|
||||
getFrequencyController().setFrequency(getFrequencyController().getFrequency());
|
||||
}
|
||||
catch(UsbException e)
|
||||
{
|
||||
@@ -367,7 +367,7 @@ public class HackRFTunerController extends USBTunerController
|
||||
|
||||
try
|
||||
{
|
||||
setFrequency(hackRFConfig.getFrequency());
|
||||
getFrequencyController().setFrequency(hackRFConfig.getFrequency());
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
@@ -511,7 +511,7 @@ public class HackRFTunerController extends USBTunerController
|
||||
{
|
||||
setSampleRateManual(rate.getRate(), 1);
|
||||
|
||||
mFrequencyController.setSampleRate(rate.getRate());
|
||||
getFrequencyController().setSampleRate(rate.getRate());
|
||||
|
||||
setBasebandFilter(rate.getFilter());
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ public class HackRFTunerEditor extends TunerConfigurationEditor
|
||||
|
||||
try
|
||||
{
|
||||
mController.setFrequencyCorrection( value );
|
||||
mController.getFrequencyController().setFrequencyCorrection( value );
|
||||
save();
|
||||
}
|
||||
catch ( SourceException e1 )
|
||||
|
||||
@@ -1,111 +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 source.tuner.manager;
|
||||
|
||||
import sample.Broadcaster;
|
||||
import sample.Listener;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import sample.complex.IComplexBufferProvider;
|
||||
import source.SourceEvent;
|
||||
import source.tuner.channel.TunerChannel;
|
||||
import source.tuner.channel.TunerChannelSource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Interface to define the functionality of a source manager for handling tuner channel management, complex buffer
|
||||
* listeners, and source event listeners.
|
||||
*/
|
||||
public abstract class AbstractSourceManager implements IComplexBufferProvider, Listener<SourceEvent>
|
||||
{
|
||||
protected Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>();
|
||||
|
||||
/**
|
||||
* Indicates if this source manager can tune (ie provide a source for) the specified tuner channel
|
||||
*/
|
||||
public abstract boolean canTune(TunerChannel tunerChannel);
|
||||
|
||||
/**
|
||||
* List of tuner channels being sourced by this source manager
|
||||
*/
|
||||
public abstract List<TunerChannel> getChannels();
|
||||
|
||||
/**
|
||||
* Obtains a source for the tuner channel or returns null if the channel cannot be sourced by this tuner.
|
||||
* Note: use the canTune() method to check if a tuner channel can be sourced/tuned prior to accessing this method.
|
||||
*
|
||||
* @param tunerChannel for requested source
|
||||
* @return tuner channel source or null
|
||||
*/
|
||||
public abstract TunerChannelSource getSource(TunerChannel tunerChannel);
|
||||
|
||||
|
||||
/**
|
||||
* Releases the source and all related resources
|
||||
*/
|
||||
public abstract void releaseSource(TunerChannelSource source);
|
||||
|
||||
/**
|
||||
* Adds the listener to receive source events from this source manager
|
||||
*
|
||||
* @param sourceEventListener to add
|
||||
*/
|
||||
public abstract void addSourceEventListener(Listener<SourceEvent> sourceEventListener);
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving source events from this source manager
|
||||
* @param sourceEventListener to remove
|
||||
*/
|
||||
public abstract void removeSourceEventListener(Listener<SourceEvent> sourceEventListener);
|
||||
|
||||
/**
|
||||
* Adds the listener to receive complex buffer samples
|
||||
*/
|
||||
@Override
|
||||
public void addComplexBufferListener(Listener<ComplexBuffer> listener)
|
||||
{
|
||||
mComplexBufferBroadcaster.addListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving complex buffer samples
|
||||
*/
|
||||
@Override
|
||||
public void removeComplexBufferListener(Listener<ComplexBuffer> listener)
|
||||
{
|
||||
mComplexBufferBroadcaster.removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if there are any complex buffer listeners registered with this source manager
|
||||
*/
|
||||
@Override
|
||||
public boolean hasComplexBufferListeners()
|
||||
{
|
||||
return mComplexBufferBroadcaster.hasListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the buffer to any registered listeners
|
||||
*/
|
||||
protected void broadcast(ComplexBuffer complexBuffer)
|
||||
{
|
||||
mComplexBufferBroadcaster.broadcast(complexBuffer);
|
||||
}
|
||||
}
|
||||
@@ -1,83 +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 source.tuner.manager;
|
||||
|
||||
import dsp.filter.channelizer.PolyphaseChannelManager;
|
||||
import sample.Listener;
|
||||
import source.SourceEvent;
|
||||
import source.tuner.channel.TunerChannel;
|
||||
import source.tuner.channel.TunerChannelSource;
|
||||
import source.tuner.TunerController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TunerSourceManager extends AbstractSourceManager
|
||||
{
|
||||
private PolyphaseChannelManager mPolyphaseChannelManager;
|
||||
private TunerController mTunerController;
|
||||
|
||||
public TunerSourceManager(TunerController tunerController)
|
||||
{
|
||||
mTunerController = tunerController;
|
||||
}
|
||||
|
||||
//TODO: broadcast a tuner event each time we add a new channel to the polyphase manager
|
||||
|
||||
@Override
|
||||
public boolean canTune(TunerChannel tunerChannel)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TunerChannel> getChannels()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunerChannelSource getSource(TunerChannel tunerChannel)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseSource(TunerChannelSource source)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSourceEventListener(Listener<SourceEvent> sourceEventListener)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSourceEventListener(Listener<SourceEvent> sourceEventListener)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(SourceEvent sourceEvent)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -129,7 +129,7 @@ public class E4KTunerController extends RTL2832TunerController
|
||||
setSampleRate(sampleRate);
|
||||
|
||||
double correction = e4kConfig.getFrequencyCorrection();
|
||||
setFrequencyCorrection(correction);
|
||||
getFrequencyController().setFrequencyCorrection(correction);
|
||||
|
||||
E4KGain masterGain = e4kConfig.getMasterGain();
|
||||
setGain(masterGain, true);
|
||||
@@ -148,7 +148,7 @@ public class E4KTunerController extends RTL2832TunerController
|
||||
|
||||
try
|
||||
{
|
||||
setFrequency(e4kConfig.getFrequency());
|
||||
getFrequencyController().setFrequency(e4kConfig.getFrequency());
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
@@ -354,7 +354,7 @@ public class E4KTunerController extends RTL2832TunerController
|
||||
* to rounding errors, 52 mhz becomes 51.999993, so we need to adjust
|
||||
* x to get 52.000003 mhz ... otherwise the PLL won't lock on the freq
|
||||
*/
|
||||
if(actualFrequency < getMinFrequency())
|
||||
if(actualFrequency < getFrequencyController().getMinimumFrequency())
|
||||
{
|
||||
x++;
|
||||
actualFrequency = calculateActualFrequency(pll, z, x);
|
||||
|
||||
@@ -184,7 +184,7 @@ public class E4KTunerEditor extends TunerConfigurationEditor
|
||||
|
||||
try
|
||||
{
|
||||
mController.setFrequencyCorrection( value );
|
||||
mController.getFrequencyController().setFrequencyCorrection( value );
|
||||
save();
|
||||
}
|
||||
catch ( SourceException e1 )
|
||||
|
||||
@@ -104,7 +104,7 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
setSampleRate(sampleRate);
|
||||
|
||||
double correction = config.getFrequencyCorrection();
|
||||
setFrequencyCorrection(correction);
|
||||
getFrequencyController().setFrequencyCorrection(correction);
|
||||
|
||||
R820TGain masterGain = config.getMasterGain();
|
||||
setGain(masterGain, true);
|
||||
@@ -123,7 +123,7 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
|
||||
try
|
||||
{
|
||||
setFrequency(config.getFrequency());
|
||||
getFrequencyController().setFrequency(config.getFrequency());
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
|
||||
@@ -191,7 +191,7 @@ public class R820TTunerEditor extends TunerConfigurationEditor
|
||||
|
||||
try
|
||||
{
|
||||
mController.setFrequencyCorrection( value );
|
||||
mController.getFrequencyController().setFrequencyCorrection( value );
|
||||
}
|
||||
catch ( SourceException e1 )
|
||||
{
|
||||
|
||||
@@ -430,9 +430,9 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ComplexBuff
|
||||
|
||||
//Fire frequency and sample rate change events so that the spectrum
|
||||
//and overlay panels can synchronize
|
||||
process(SourceEvent.frequencyChange(mTuner.getTunerController().getFrequency()));
|
||||
process(SourceEvent.frequencyChange(mTuner.getTunerController().getFrequencyController().getFrequency()));
|
||||
|
||||
process(SourceEvent.sampleRateChange(mTuner.getTunerController().getSampleRate()));
|
||||
process(SourceEvent.sampleRateChange(mTuner.getTunerController().getFrequencyController().getSampleRate()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user