diff --git a/src/controller/channel/Channel.java b/src/controller/channel/Channel.java index 0ac11a9e..3503a0e7 100644 --- a/src/controller/channel/Channel.java +++ b/src/controller/channel/Channel.java @@ -1004,7 +1004,7 @@ public class Channel extends Configuration TunerChannel tunerChannel = getTunerChannel(); return tunerChannel != null && - tunerChannel.isWithin( minimum, maximum ); + tunerChannel.overlaps( minimum, maximum ); } public void addTrafficChannel( String channelID, Channel channel ) diff --git a/src/source/tuner/TunerChannel.java b/src/source/tuner/TunerChannel.java index c873753c..7ff6e32d 100644 --- a/src/source/tuner/TunerChannel.java +++ b/src/source/tuner/TunerChannel.java @@ -1,6 +1,6 @@ /******************************************************************************* * SDR Trunk - * Copyright (C) 2014 Dennis Sheirer + * Copyright (C) 2014,2015 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 @@ -71,13 +71,17 @@ public class TunerChannel implements Comparable } /** - * Indicates if any part of this tuner channel is contained within the - * minimum and maximum frequency values. + * Indicates if any part of this tuner channel overlaps the specified range */ - public boolean isWithin( long minimum, long maximum ) + public boolean overlaps( long minimum, long maximum ) { - return ( ( minimum <= getMinFrequency() && getMinFrequency() <= maximum ) || - ( minimum <= getMaxFrequency() && getMaxFrequency() <= maximum ) ); + long channelMin = getMinFrequency(); + long channelMax = getMaxFrequency(); + + return ( ( minimum <= channelMin && channelMin <= maximum ) || + ( minimum <= channelMax && channelMax <= maximum ) || + ( minimum <= channelMin && channelMax <= maximum ) || + ( channelMin <= minimum && maximum <= channelMax ) ); } public String toString() @@ -85,9 +89,9 @@ public class TunerChannel implements Comparable StringBuilder sb = new StringBuilder(); sb.append( "Channel: " ); - sb.append( ( mFrequency - ( mBandwidth / 2 ) ) ); + sb.append( getMinFrequency() ); sb.append( "-" ); - sb.append( ( mFrequency + ( mBandwidth / 2 ) ) ); + sb.append( getMaxFrequency() ); return sb.toString(); } diff --git a/src/source/tuner/TunerController.java b/src/source/tuner/TunerController.java index 0676e4f6..1cc26637 100644 --- a/src/source/tuner/TunerController.java +++ b/src/source/tuner/TunerController.java @@ -1,6 +1,6 @@ /******************************************************************************* * SDR Trunk - * Copyright (C) 2014 Dennis Sheirer + * Copyright (C) 2014,2015 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 @@ -19,7 +19,6 @@ package source.tuner; import java.util.ArrayList; import java.util.Collections; -import java.util.Iterator; import java.util.concurrent.RejectedExecutionException; import org.slf4j.Logger; @@ -39,25 +38,34 @@ public abstract class TunerController implements Tunable /* List of currently tuned channels being served to demod channels */ protected ArrayList mTunedChannels = new ArrayList(); - 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 minimumFrequency minimum uncorrected tunable frequency + * @param maximumFrequency maximum uncorrected tunable frequency + * @param middleUnusable is the +/- value center DC spike to avoid for channels + * @param extantUnusable 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 ) + public TunerController( long minimumFrequency, + long maximumFrequency, + int middleUnusable, + double usableBandwidthPercentage ) { mFrequencyController = new FrequencyController( this, minimumFrequency, maximumFrequency, 0.0d ); + mMiddleUnusable = middleUnusable; + mUsableBandwidthPercentage = usableBandwidthPercentage; } /** @@ -112,24 +120,21 @@ public abstract class TunerController implements Tunable return mFrequencyController.getMaximumFrequency(); } - public long getMinTunedFrequency() + private long getMinTunedFrequency() throws SourceException { - return mFrequencyController.getFrequency() - - ( mFrequencyController.getBandwidth() / 2 ); + return mFrequencyController.getFrequency() - ( getUsableBandwidth() / 2 ); } - public long getMaxTunedFrequency() + private long getMaxTunedFrequency() throws SourceException { - return mFrequencyController.getFrequency() + - ( mFrequencyController.getBandwidth() / 2 ); + return mFrequencyController.getFrequency() + ( getUsableBandwidth() / 2 ); } /** - * Indicates if the tuner can accomodate this new channel frequency and - * bandwidth, along with all of the existing tuned channels currently in - * place. + * Indicates if channel along with all of the other currently sourced + * channels can fit within the tunable bandwidth. */ - public boolean canTuneChannel( TunerChannel channel ) + private boolean canTune( TunerChannel channel ) { //Make sure we're within the tunable frequency range of this tuner if( getMinFrequency() < channel.getMinFrequency() && @@ -145,19 +150,27 @@ public abstract class TunerController implements Tunable //Sort the existing locks and get the min/max locked frequencies Collections.sort( mTunedChannels ); + int usableBandwidth = getUsableBandwidth(); long minLockedFrequency = mTunedChannels.get( 0 ).getMinFrequency(); long maxLockedFrequency = mTunedChannels .get( mTunedChannels.size() - 1 ).getMaxFrequency(); - //Requested channel is higher than min locked frequency + //Requested channel is within current locked channel frequency range if( minLockedFrequency <= channel.getMinFrequency() && - ( channel.getMaxFrequency() - minLockedFrequency ) <= getBandwidth() ) + 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 - else if( channel.getMaxFrequency() <= maxLockedFrequency && - ( maxLockedFrequency - channel.getMinFrequency() ) <= getBandwidth() ) + if( channel.getMinFrequency() <= maxLockedFrequency && + maxLockedFrequency - channel.getMinFrequency() <= usableBandwidth ) { return true; } @@ -166,26 +179,72 @@ public abstract class TunerController implements Tunable 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 threadPoolManager for the channel to use for runnables + * @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( ThreadPoolManager threadPoolManager, - Tuner tuner, TunerChannel tunerChannel ) - throws RejectedExecutionException, SourceException + Tuner tuner, TunerChannel channel ) throws RejectedExecutionException { TunerChannelSource source = null; - if( canTuneChannel( tunerChannel ) ) + if( canTune( channel ) ) { - mTunedChannels.add( tunerChannel ); - - updateLOFrequency(); - - source = new TunerChannelSource( threadPoolManager, - tuner, tunerChannel ); + try + { + mTunedChannels.add( channel ); + + if( requiresLOUpdate( channel ) ) + { + updateLOFrequency(); + } + + source = new TunerChannelSource( threadPoolManager, + tuner, channel ); + } + catch( SourceException se ) + { + mTunedChannels.remove( channel ); + source = null; + } } return source; } - + + /** + * 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 ) @@ -194,56 +253,115 @@ public abstract class TunerController implements Tunable } else { - mLog.error( "Tuner Controller - couldn't find the tuned channel " - + "to release it" ); + mLog.error( "Request to release channel that is not in current tuned channels set" ); } } /** - * Sets the Local Oscillator frequency to the middle of the currently - * locked frequency range, adjusting the left/right of a channel, if the - * middle falls within the locked range of any of the channels. Note: this - * will fail to set the correct frequency if multiple overlapping channel - * bandwidths are locked in the exact middle of the total locked channel - * frequency range. + * 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 + * @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. */ - public void updateLOFrequency() throws SourceException + private void updateLOFrequency() throws SourceException { Collections.sort( mTunedChannels ); - long minLockedFrequency = mTunedChannels.get( 0 ).getMinFrequency(); - long maxLockedFrequency = mTunedChannels - .get( mTunedChannels.size() - 1 ).getMaxFrequency(); + long frequency = getFrequency(); + + boolean frequencyValid = true; - long middle = minLockedFrequency + - ( ( maxLockedFrequency - minLockedFrequency ) / 2 ); - long middleMin = middle - 10000; - long middleMax = middle + 10000; - - Iterator it = mTunedChannels.iterator(); - - while( it.hasNext() ) + //If there is only 1 channel, position it to the right of center + if( mTunedChannels.size() == 1 ) { - TunerChannel lock = it.next(); + frequency = mTunedChannels.get( 0 ).getMinFrequency() - mMiddleUnusable; + } + else + { + long minLockedFrequency = mTunedChannels.get( 0 ).getMinFrequency(); + long maxLockedFrequency = mTunedChannels + .get( mTunedChannels.size() - 1 ).getMaxFrequency(); - //If a locked channel overlaps our middle frequency lockout, adjust to - //the left or the right of that channel, whichever is closer - if( lock.getMinFrequency() < middleMax && middleMin < lock.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 ) { - if( middleMax - lock.getMinFrequency() < lock.getMaxFrequency() - middleMin ) + boolean processingRequired = true; + + while( frequencyValid && processingRequired ) { - middle = lock.getMinFrequency() - 10000; - } - else - { - middle = lock.getMaxFrequency() + 10000; + 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; + } + } } } } - - mFrequencyController.setFrequency( middle ); + + 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 ); } /** diff --git a/src/source/tuner/airspy/AirspyTunerController.java b/src/source/tuner/airspy/AirspyTunerController.java index 16577a55..9c8b9a1f 100644 --- a/src/source/tuner/airspy/AirspyTunerController.java +++ b/src/source/tuner/airspy/AirspyTunerController.java @@ -93,6 +93,7 @@ public class AirspyTunerController extends TunerController public static final long FREQUENCY_MIN = 24000000l; public static final long FREQUENCY_MAX = 1800000000l; public static final long FREQUENCY_DEFAULT = 101100000; + public static final double USABLE_BANDWIDTH_PERCENT = 0.90; public static final AirspySampleRate DEFAULT_SAMPLE_RATE = new AirspySampleRate( 0, 10000000, "10.00 MHz" ); @@ -136,7 +137,7 @@ public class AirspyTunerController extends TunerController public AirspyTunerController( Device device, ThreadPoolManager threadPoolManager ) throws SourceException { - super( FREQUENCY_MIN, FREQUENCY_MAX ); + super( FREQUENCY_MIN, FREQUENCY_MAX, 0, USABLE_BANDWIDTH_PERCENT ); mDevice = device; mThreadPoolManager = threadPoolManager; @@ -829,121 +830,6 @@ public class AirspyTunerController extends TunerController } } - /** - * Indicates if the tuner can accommodate this new channel frequency and - * bandwidth, along with all of the existing tuned channels currently in - * place. - */ - public boolean canTuneChannel( TunerChannel channel ) - { - //If this is the first lock, then we're good - if( mTunedChannels.isEmpty() ) - { - return true; - } - else - { - //Sort the existing locks and get the min/max locked frequencies - Collections.sort( mTunedChannels ); - - long minLockedFrequency = mTunedChannels.get( 0 ).getMinFrequency(); - long maxLockedFrequency = mTunedChannels - .get( mTunedChannels.size() - 1 ).getMaxFrequency(); - - if( channel.getMinFrequency() >= minLockedFrequency && - channel.getMaxFrequency() <= maxLockedFrequency ) - { - return true; - } - - int usable = getUsableBandwidth(); - - if( channel.getMinFrequency() < minLockedFrequency ) - { - return ( maxLockedFrequency - channel.getMinFrequency() ) <= usable; - } - - if( channel.getMaxFrequency() > maxLockedFrequency ) - { - return ( channel.getMaxFrequency() - minLockedFrequency ) <= usable; - } - } - - return false; - } - - /** - * Limits the usable bandwidth of the tuner to 90% of the bandwidth to - * account for roll-off at the band edges - */ - private int getUsableBandwidth() - { - return (int)( (double)getBandwidth() * 0.9d ); - } - - /** - * Overrides the default tuner controller method to limit the total bandwidth - * of the tuner to 90% and adjust the min tuned frequency accordingly - */ - public long getMinTunedFrequency() - { - return mFrequencyController.getFrequency() - ( getUsableBandwidth() / 2 ); - } - - /** - * Overrides the default tuner controller method to limit the total bandwidth - * of the tuner to 90% and adjust the max tuned frequency accordingly - */ - public long getMaxTunedFrequency() - { - return mFrequencyController.getFrequency() + ( getUsableBandwidth() / 2 ); - } - - public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager, - Tuner tuner, TunerChannel tunerChannel ) - throws RejectedExecutionException, SourceException - { - TunerChannelSource source = null; - - if( canTuneChannel( tunerChannel ) ) - { - mTunedChannels.add( tunerChannel ); - - Collections.sort( mTunedChannels ); - - long min = mTunedChannels.get( 0 ).getMinFrequency(); - long max = mTunedChannels.get( mTunedChannels.size() - 1 ).getMaxFrequency(); - - /* Adjust the center frequency if the set of tuner channels don't - * currently fit within the usable bandwidth and current center frequency */ - if( !( getMinTunedFrequency() <= min && max <= getMaxTunedFrequency() ) ) - { - long freq = min + ( ( max - min ) / 2 ); - - mFrequencyController.setFrequency( freq ); - } - - source = new TunerChannelSource( threadPoolManager, - tuner, tunerChannel ); - } - - return source; - } - - public void releaseChannel( TunerChannelSource tunerChannelSource ) - { - if( tunerChannelSource != null ) - { - mTunedChannels.remove( tunerChannelSource.getTunerChannel() ); - } - else - { - mLog.error( "Tuner Controller - couldn't find the tuned channel " - + "to release it" ); - } - } - - /** * Airspy Board Identifier */ diff --git a/src/source/tuner/fcd/FCDTunerController.java b/src/source/tuner/fcd/FCDTunerController.java index 03a49b56..cead5092 100644 --- a/src/source/tuner/fcd/FCDTunerController.java +++ b/src/source/tuner/fcd/FCDTunerController.java @@ -46,6 +46,9 @@ public abstract class FCDTunerController extends TunerController private final static Logger mLog = LoggerFactory.getLogger( FCDTunerController.class ); + public static final double USABLE_BANDWIDTH_PERCENT = 1.0; + public static final int DC_SPIKE_AVOID_BUFFER = 2000; + public final static byte FCD_INTERFACE = (byte)0x2; public final static byte FCD_ENDPOINT_IN = (byte)0x82; public final static byte FCD_ENDPOINT_OUT = (byte)0x2; @@ -70,7 +73,10 @@ public abstract class FCDTunerController extends TunerController int minTunableFrequency, int maxTunableFrequency ) { - super( minTunableFrequency, maxTunableFrequency ); + super( minTunableFrequency, + maxTunableFrequency, + DC_SPIKE_AVOID_BUFFER, + USABLE_BANDWIDTH_PERCENT ); mDevice = device; mDeviceDescriptor = descriptor; diff --git a/src/source/tuner/hackrf/HackRFTunerController.java b/src/source/tuner/hackrf/HackRFTunerController.java index 8d27e7f4..bdc3bce2 100644 --- a/src/source/tuner/hackrf/HackRFTunerController.java +++ b/src/source/tuner/hackrf/HackRFTunerController.java @@ -108,6 +108,8 @@ public class HackRFTunerController extends TunerController public static final long MIN_FREQUENCY = 10000000l; public static final long MAX_FREQUENCY = 6000000000l; public static final long DEFAULT_FREQUENCY = 101100000; + public static final double USABLE_BANDWIDTH_PERCENT = 0.95; + public static final int DC_SPIKE_AVOID_BUFFER = 5000; public final static int TRANSFER_BUFFER_POOL_SIZE = 16; @@ -131,7 +133,7 @@ public class HackRFTunerController extends TunerController public HackRFTunerController( Device device, DeviceDescriptor descriptor, ThreadPoolManager threadPoolManager ) throws SourceException { - super( MIN_FREQUENCY, MAX_FREQUENCY ); + super( MIN_FREQUENCY, MAX_FREQUENCY, DC_SPIKE_AVOID_BUFFER, USABLE_BANDWIDTH_PERCENT ); mDevice = device; mDeviceDescriptor = descriptor; diff --git a/src/source/tuner/rtl/RTL2832TunerController.java b/src/source/tuner/rtl/RTL2832TunerController.java index d38a8cfb..c9eb524e 100644 --- a/src/source/tuner/rtl/RTL2832TunerController.java +++ b/src/source/tuner/rtl/RTL2832TunerController.java @@ -137,9 +137,14 @@ public abstract class RTL2832TunerController extends TunerController DeviceDescriptor deviceDescriptor, ThreadPoolManager threadPoolManager, long minTunableFrequency, - long maxTunableFrequency ) throws SourceException + long maxTunableFrequency, + int centerUnusableBandwidth, + double usableBandwidthPercentage ) throws SourceException { - super( minTunableFrequency, maxTunableFrequency ); + super( minTunableFrequency, + maxTunableFrequency, + centerUnusableBandwidth, + usableBandwidthPercentage ); mThreadPoolManager = threadPoolManager; diff --git a/src/source/tuner/rtl/e4k/E4KTunerController.java b/src/source/tuner/rtl/e4k/E4KTunerController.java index 57a58578..6bdfb149 100644 --- a/src/source/tuner/rtl/e4k/E4KTunerController.java +++ b/src/source/tuner/rtl/e4k/E4KTunerController.java @@ -44,6 +44,8 @@ public class E4KTunerController extends RTL2832TunerController public static final long MIN_FREQUENCY = 52000000; public static final long MAX_FREQUENCY = 2200000000l; + public static final double USABLE_BANDWIDTH_PERCENT = 0.95; + public static final int DC_SPIKE_AVOID_BUFFER = 15000; /* The local oscillator is defined by whole (integer) units of the oscillator * frequency and fractional units representing 1/65536th of the oscillator @@ -115,8 +117,8 @@ public class E4KTunerController extends RTL2832TunerController ThreadPoolManager threadPoolManager ) throws SourceException { - super( device, deviceDescriptor, threadPoolManager, - MIN_FREQUENCY, MAX_FREQUENCY ); + super( device, deviceDescriptor, threadPoolManager, MIN_FREQUENCY, + MAX_FREQUENCY, DC_SPIKE_AVOID_BUFFER, USABLE_BANDWIDTH_PERCENT ); } @Override diff --git a/src/source/tuner/rtl/r820t/R820TTunerController.java b/src/source/tuner/rtl/r820t/R820TTunerController.java index 4d8ec9ce..607482d4 100644 --- a/src/source/tuner/rtl/r820t/R820TTunerController.java +++ b/src/source/tuner/rtl/r820t/R820TTunerController.java @@ -47,6 +47,9 @@ public class R820TTunerController extends RTL2832TunerController public static final long MIN_FREQUENCY = 3180000; public static final long MAX_FREQUENCY = 1782030000; + public static final double USABLE_BANDWIDTH_PERCENT = 1.0; + public static final int DC_SPIKE_AVOID_BUFFER = 2500; + public static final int R820T_IF_FREQUENCY = 3570000; public static final byte VERSION = (byte)49; @@ -78,8 +81,8 @@ public class R820TTunerController extends RTL2832TunerController ThreadPoolManager threadPoolManager ) throws SourceException { - super( device, deviceDescriptor, threadPoolManager, - MIN_FREQUENCY, MAX_FREQUENCY ); + super( device, deviceDescriptor, threadPoolManager, MIN_FREQUENCY, + MAX_FREQUENCY, DC_SPIKE_AVOID_BUFFER, USABLE_BANDWIDTH_PERCENT ); } @Override diff --git a/src/spectrum/SpectralDisplayPanel.java b/src/spectrum/SpectralDisplayPanel.java index 4b1252c6..dce94b2c 100644 --- a/src/spectrum/SpectralDisplayPanel.java +++ b/src/spectrum/SpectralDisplayPanel.java @@ -28,6 +28,7 @@ import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import java.text.DecimalFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Hashtable; import javax.swing.JCheckBoxMenuItem;