diff --git a/src/source/tuner/TunerController.java b/src/source/tuner/TunerController.java
index 017231bd..9290ab11 100644
--- a/src/source/tuner/TunerController.java
+++ b/src/source/tuner/TunerController.java
@@ -1,29 +1,24 @@
/*******************************************************************************
* SDR Trunk
- * Copyright (C) 2014-2016 Dennis Sheirer
- *
+ * Copyright (C) 2014-2017 Dennis Sheirer
+ *
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
******************************************************************************/
package source.tuner;
-import java.util.SortedSet;
-import java.util.concurrent.ConcurrentSkipListSet;
-import java.util.concurrent.RejectedExecutionException;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-
import source.SourceException;
import source.tuner.configuration.TunerConfiguration;
import source.tuner.frequency.FrequencyChangeEvent;
@@ -32,365 +27,344 @@ import source.tuner.frequency.FrequencyController;
import source.tuner.frequency.FrequencyController.Tunable;
import source.tuner.frequency.IFrequencyChangeProcessor;
+import java.util.SortedSet;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.concurrent.RejectedExecutionException;
+
public abstract class TunerController implements Tunable, IFrequencyChangeProcessor
{
- private final static Logger mLog =
- LoggerFactory.getLogger( TunerController.class );
+ private final static Logger mLog = LoggerFactory.getLogger(TunerController.class);
- /* List of currently tuned channels being served to demod channels */
- private SortedSet mTunedChannels = new ConcurrentSkipListSet<>();
- protected FrequencyController mFrequencyController;
- private int mMiddleUnusable;
- private double mUsableBandwidthPercentage;
-
- /**
- * Abstract tuner controller class. The tuner controller manages frequency
- * bandwidth and currently tuned channels that are being fed samples from
- * the tuner.
- *
- * @param minimumFrequency minimum uncorrected tunable frequency
- * @param maximumFrequency maximum uncorrected tunable frequency
- * @param middleUnusable is the +/- value center DC spike to avoid for channels
- * @param usableBandwidthPercentage is the unusable space at the extreme ends of the spectrum
- *
- * @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 usableBandwidthPercentage )
- {
- mFrequencyController = new FrequencyController( this,
- minimumFrequency,
- maximumFrequency,
- 0.0d );
- mMiddleUnusable = middleUnusable;
- mUsableBandwidthPercentage = usableBandwidthPercentage;
- }
-
- /**
- * Applies the settings in the tuner configuration
- */
- public abstract void apply( TunerConfiguration config )
- throws SourceException;
+ /* List of currently tuned channels being served to demod channels */
+ private SortedSet mTunedChannels = new ConcurrentSkipListSet<>();
+ protected FrequencyController mFrequencyController;
+ private int mMiddleUnusable;
+ private double mUsableBandwidthPercentage;
- /**
- * Responds to requests to set the frequency
- */
- @Override
- public void frequencyChanged( FrequencyChangeEvent event ) throws SourceException
- {
- if( event.getEvent() == Event.REQUEST_FREQUENCY_CHANGE )
- {
- setFrequency( event.getValue().longValue() );
- }
- }
-
- public int getBandwidth()
+ /**
+ * Abstract tuner controller class. The tuner controller manages frequency bandwidth and currently tuned channels
+ * that are being fed samples from the tuner.
+ *
+ * @param minimumFrequency minimum uncorrected tunable frequency
+ * @param maximumFrequency maximum uncorrected tunable frequency
+ * @param middleUnusable is the +/- value center DC spike to avoid for channels
+ * @param usableBandwidth percentage of usable bandwidth relative to space at the extreme ends of the spectrum
+ * @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)
{
- return mFrequencyController.getBandwidth();
+ mFrequencyController = new FrequencyController(this, minimumFrequency, maximumFrequency, 0.0d);
+ mMiddleUnusable = middleUnusable;
+ mUsableBandwidthPercentage = usableBandwidth;
}
-
+
+ /**
+ * Applies the settings in the tuner configuration
+ */
+ public abstract void apply(TunerConfiguration config) throws SourceException;
+
+ /**
+ * Responds to requests to set the frequency
+ */
+ @Override
+ public void frequencyChanged(FrequencyChangeEvent event) throws SourceException
+ {
+ if(event.getEvent() == Event.REQUEST_FREQUENCY_CHANGE)
+ {
+ setFrequency(event.getValue().longValue());
+ }
+ }
+
+ 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();
- }
-
- public int getSampleRate()
- {
- return mFrequencyController.getBandwidth();
- }
-
- 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 TunerChannelSource( 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
- * accomodate the tuner channel
- */
- private boolean requiresLOUpdate( TunerChannel channel ) throws SourceException
- {
- return !( getMinTunedFrequency() <= channel.getMinFrequency() &&
- channel.getMaxFrequency() <= getMaxTunedFrequency() &&
- ( ( mMiddleUnusable == 0 ) ||
- ( !channel.overlaps( getTunedFrequency() - mMiddleUnusable,
- getTunedFrequency() + mMiddleUnusable ) ) ) );
- }
-
- /**
- * 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 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 frequency = getFrequency();
-
- boolean frequencyValid = true;
-
- //If there is only 1 channel, position it to the right of center
- if( mTunedChannels.size() == 1 )
- {
- frequency = mTunedChannels.first().getMinFrequency() - mMiddleUnusable;
- }
- 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
- frequency = 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( frequencyValid && processingRequired )
- {
- processingRequired = false;
-
- long minAvoid = frequency - mMiddleUnusable;
- long maxAvoid = frequency + mMiddleUnusable;
-
- for( TunerChannel channel: mTunedChannels )
- {
- if( channel.overlaps( minAvoid, maxAvoid ) )
- {
- //Can we move the frequency lower so that the
- //channel sits to the right of center?
- long adjustment = channel.getMaxFrequency() - minAvoid;
-
- if( frequency + adjustment -
- ( getUsableBandwidth() / 2 ) <= minLockedFrequency )
- {
- frequency += adjustment;
- processingRequired = true;
- }
- else
- {
- frequencyValid = false;
- }
-
- //break out of the for/each loop, so that we can
- //start over again with all of the channels
- break;
- }
- }
- }
- }
- }
-
- if( frequencyValid )
- {
- mFrequencyController.setFrequency( frequency );
- }
- 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 );
- }
-
- /**
- * Sets the listener to be notified any time that the tuner changes frequency
- * or bandwidth/sample rate.
- *
- * Note: this is normally used by the Tuner. Any additional listeners can
- * be registered on the tuner.
- */
- public void addListener( IFrequencyChangeProcessor processor )
+ public void setFrequency(long frequency) throws SourceException
{
- mFrequencyController.addListener( processor );
+ mFrequencyController.setFrequency(frequency);
+ }
+
+ /**
+ * Gets the center frequency of the local oscillator
+ *
+ * @return frequency in hertz
+ */
+ public long getFrequency()
+ {
+ return mFrequencyController.getFrequency();
+ }
+
+ public int getSampleRate()
+ {
+ return mFrequencyController.getBandwidth();
+ }
+
+ 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 TunerChannelSource(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);
+ }
+
+ /**
+ * Sets the listener to be notified any time that the tuner changes frequency or bandwidth/sample rate.
+ *
+ * Note: this is normally used by the Tuner. Any additional listeners can be registered on the tuner.
+ */
+ public void addListener(IFrequencyChangeProcessor processor)
+ {
+ mFrequencyController.addListener(processor);
}
/**
* Removes the frequency change listener
*/
- public void removeListener( IFrequencyChangeProcessor processor )
+ public void removeListener(IFrequencyChangeProcessor processor)
{
- mFrequencyController.removeFrequencyChangeProcessor( processor );
+ mFrequencyController.removeFrequencyChangeProcessor(processor);
}
}