#39 WIP - changes tuner controllers to expose the frequency controller

and the channel manager in preparation for polyphase.
This commit is contained in:
Denny
2017-12-11 20:27:01 -05:00
parent f38184fd83
commit 318e6f3586
39 changed files with 547 additions and 2875 deletions

View File

@@ -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;
}
/**

View File

@@ -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");
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
});
}
}

View File

@@ -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
{

View File

@@ -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() + "]");
}
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;

View File

@@ -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);
}
/**

View File

@@ -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
{

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -208,7 +208,7 @@ public class AirspyTunerEditor extends TunerConfigurationEditor
try
{
mController.setFrequencyCorrection( value );
mController.getFrequencyController().setFrequencyCorrection( value );
}
catch ( SourceException e1 )
{

View 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");
}
}
}

View File

@@ -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
{

View File

@@ -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" );
}
}

View File

@@ -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");
}
}
/**

View File

@@ -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());
}
/**

View File

@@ -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 ) );
}

View File

@@ -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)
{

View File

@@ -149,7 +149,7 @@ public class FCD1TunerEditor extends TunerConfigurationEditor
try
{
mController.setFrequencyCorrection( value );
mController.getFrequencyController().setFrequencyCorrection( value );
save();
}
catch ( SourceException e1 )

View File

@@ -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)
{

View File

@@ -141,7 +141,7 @@ public class FCD2TunerEditor extends TunerConfigurationEditor
try
{
mController.setFrequencyCorrection( value );
mController.getFrequencyController().setFrequencyCorrection( value );
save();
}
catch ( SourceException e1 )

View File

@@ -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;
}
}

View File

@@ -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());

View File

@@ -179,7 +179,7 @@ public class HackRFTunerEditor extends TunerConfigurationEditor
try
{
mController.setFrequencyCorrection( value );
mController.getFrequencyController().setFrequencyCorrection( value );
save();
}
catch ( SourceException e1 )

View File

@@ -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);
}
}

View File

@@ -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)
{
}
}

View File

@@ -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);

View File

@@ -184,7 +184,7 @@ public class E4KTunerEditor extends TunerConfigurationEditor
try
{
mController.setFrequencyCorrection( value );
mController.getFrequencyController().setFrequencyCorrection( value );
save();
}
catch ( SourceException e1 )

View File

@@ -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)
{

View File

@@ -191,7 +191,7 @@ public class R820TTunerEditor extends TunerConfigurationEditor
try
{
mController.setFrequencyCorrection( value );
mController.getFrequencyController().setFrequencyCorrection( value );
}
catch ( SourceException e1 )
{

View File

@@ -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()));
}
}