Resolves #170 adding support for Airspy Mini tuner's 3 and 6 MHz

sample rates.

Resolves issue in PolyphaseChannelManager responding to sample rate
changes in the tuner.  The manager now recreates the channelizer when
needed if the tuner's sample rate has changed since the last time the
channelizer was used.

Updates the Tuner selection view to reflect tuner lock state.  Updates
the tuner editors to enable/disable frequency controller and sample
rate controls according to the tuner's lock state.
This commit is contained in:
Denny
2018-07-14 07:45:42 -04:00
parent 9e7fa7618d
commit 104c4f72be
18 changed files with 376 additions and 348 deletions

View File

@@ -68,9 +68,10 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener<Re
* Sets the input sample rate for for this channelizer
* @param sampleRate in hertz
*/
public void setSampleRate(double sampleRate)
public void setRates(double sampleRate, int channelCount)
{
mSampleRate = sampleRate;
mChannelCount = channelCount;
mChannelSampleRate = mSampleRate / (double)mChannelCount;
}

View File

@@ -20,7 +20,6 @@ import io.github.dsheirer.source.tuner.channel.TunerChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
@@ -43,7 +42,7 @@ public class ChannelCalculator
private int mChannelCount;
private double mCenterFrequency;
private double mChannelBandwidth;
private double mOversampling = 1.0;
private double mOversampling = 2.0;
/**
* Calculates channel index(es) from a polyphase channelizer output to source a DDC polyphase channel source
@@ -137,9 +136,11 @@ public class ChannelCalculator
*
* @param sampleRate to use for channel count
*/
public void setSampleRate(double sampleRate)
public void setRates(double sampleRate, int channelCount)
{
setChannelCount((int)(sampleRate / getChannelBandwidth()));
mSampleRate = sampleRate;
mChannelCount = channelCount;
updateChannelBandwidth();
}
/**
@@ -544,7 +545,8 @@ public class ChannelCalculator
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Channels:").append(getChannelCount());
sb.append("Channel Calculator Settings");
sb.append(" Channels:").append(getChannelCount());
sb.append(" Sample Rate:").append(getSampleRate());
sb.append(" Channel Bandwidth:").append(getChannelBandwidth());
sb.append(" Channel Rate:").append(getChannelSampleRate());
@@ -552,43 +554,4 @@ public class ChannelCalculator
sb.append(" Max:").append(getMaximumFrequency());
return sb.toString();
}
public static void main(String[] args)
{
mLog.debug("Starting ....");
double channelBandwidth = 12500.0;
double sampleRate = 100000.0; //Airspy
int channelCount = (int)(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

@@ -145,7 +145,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
* @param sampleRate to channelize
* @return number of multiple of two channels that can be channelized.
*/
private static int getChannelCount(double sampleRate)
public static int getChannelCount(double sampleRate)
{
int channels = (int)(sampleRate / DEFAULT_MINIMUM_CHANNEL_BANDWIDTH);
@@ -164,12 +164,11 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
* @param sampleRate in hertz
*/
@Override
public void setSampleRate(double sampleRate)
public void setRates(double sampleRate, int channelCount)
{
try
{
super.setSampleRate(sampleRate);
super.setRates(sampleRate, channelCount);
float[] filterTaps = FilterFactory.getSincM2Channelizer(getChannelSampleRate(), getChannelCount(),
mTapsPerChannel, false);

View File

@@ -108,8 +108,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
mBufferProcessor = new ContinuousBufferProcessor(200, 50);
mBufferProcessor.setListener(mBufferSourceEventMonitor);
updateChannelizerSampleRate(sampleRate);
}
/**
@@ -229,6 +227,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
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());
mLog.info(mChannelCalculator.toString());
return null;
}
}
@@ -245,6 +244,8 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
{
//Note: the polyphase channel source has already been added to the mChannelSources in getChannel() method
checkChannelizerConfiguration();
mPolyphaseChannelizer.addChannel(channelSource);
mSourceEventBroadcaster.broadcast(SourceEvent.channelCountChange(getTunerChannelCount()));
@@ -312,10 +313,9 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
break;
case NOTIFICATION_SAMPLE_RATE_CHANGE:
//Update channel calculator immediately so that channels can be allocated
mChannelCalculator.setSampleRate(sourceEvent.getValue().doubleValue());
//Defer channelizer configuration changes to be handled on the buffer processor thread
mBufferSourceEventMonitor.receive(sourceEvent);
double sampleRate = sourceEvent.getValue().doubleValue();
int channelCount = (int)Math.ceil(sampleRate / MINIMUM_CHANNEL_BANDWIDTH);
mChannelCalculator.setRates(sampleRate, channelCount);
break;
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_LOCKED:
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_UNLOCKED:
@@ -325,7 +325,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
default:
mLog.info("Unrecognized source event: " + sourceEvent.toString());
break;
}
}
@@ -335,40 +334,39 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
* Note: this method should only be invoked on the mBufferProcessor thread or prior to starting the mBufferProcessor.
* Sample rate source events will normally arrive via the incoming complex buffer stream from the mBufferProcessor
* and will be handled as they arrive.
*
* @param sampleRate to use for the channelizer
*/
private void updateChannelizerSampleRate(double sampleRate)
private void checkChannelizerConfiguration()
{
if(mPolyphaseChannelizer == null || Math.abs(mPolyphaseChannelizer.getSampleRate() - sampleRate) > 0.5)
//Channel calculator is always in sync with the tuner's current sample rate
double tunerSampleRate = mChannelCalculator.getSampleRate();
//If the channelizer is not setup, or setup to the wrong sample rate, recreate it
if(mPolyphaseChannelizer == null || Math.abs(mPolyphaseChannelizer.getSampleRate() - tunerSampleRate) > 0.5)
{
if(mPolyphaseChannelizer != null && mPolyphaseChannelizer.getRegisteredChannelCount() > 0)
{
throw new IllegalStateException("Polyphase Channelizer cannot be changed to a new sample rate while " +
"channels are currently sourced. Ensure you remove all tuner channels before changing tuner " +
"sample rate. Current channel count:" +
(mPolyphaseChannelizer != null ? mPolyphaseChannelizer.getRegisteredChannelCount() : "0"));
}
try
{
if(mPolyphaseChannelizer == null)
{
mPolyphaseChannelizer = new ComplexPolyphaseChannelizerM2(sampleRate,
POLYPHASE_CHANNELIZER_TAPS_PER_CHANNEL);
}
else
{
mPolyphaseChannelizer.setSampleRate(sampleRate);
}
mPolyphaseChannelizer = new ComplexPolyphaseChannelizerM2(tunerSampleRate,
POLYPHASE_CHANNELIZER_TAPS_PER_CHANNEL);
}
catch(IllegalArgumentException iae)
{
mLog.error("Could not create polyphase channelizer for sample rate [" + sampleRate + "]", iae);
mLog.error("Could not create polyphase channelizer for sample rate [" + tunerSampleRate + "]", iae);
}
catch(FilterDesignException fde)
{
mLog.error("Could not create filter for polyphase channelizer for sample rate [" + sampleRate + "]", fde);
mLog.error("Could not create filter for polyphase channelizer for sample rate [" + tunerSampleRate + "]", fde);
}
//Clear the previous channel synthesis filters so they can be recreated for the new channel sample rate
//Clear any previous channel synthesis filters so they can be recreated for the new channel sample rate
mOutputProcessorFilters.clear();
//Broadcast the new channel sample rate
double updatedChannelSampleRate = mChannelCalculator.getChannelSampleRate();
updateOutputProcessors(SourceEvent.sampleRateChange(updatedChannelSampleRate, "Channelizer Sample Rate Update"));
}
}
@@ -600,9 +598,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
//Don't send the tuner's frequency change event down to the channels - it would cause chaos
updateOutputProcessors(null);
break;
case NOTIFICATION_SAMPLE_RATE_CHANGE:
updateChannelizerSampleRate(queuedSourceEvent.getValue().intValue());
break;
}
queuedSourceEvent = mQueuedSourceEvents.poll();