mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-11 08:07:24 -05:00
WIP - implements tuner source manager to calculate and update the tuner's
center frequency each time we allocate a new DDC/channel. Adds support for broadcasting channel count change source events so that the tuner can accurately convey sourced channel counts via tuner events. Completes changeover of polyphase channel manager to support arbitrary bandwidth channels. The new approach provides greater compatibility across any tuner sample rates. Likewise, all decoders will have to be updated to adapt to any channel sample rate, although channel bandwidths will be at least 12.5 kHz and likely no more than 25.0 kHz, with a 2x oversampling that produces channel sample rates in range 25.0 - 50.0 kHz.
This commit is contained in:
@@ -185,7 +185,7 @@ public class ChannelCalculator
|
||||
*/
|
||||
public double getChannelSampleRate()
|
||||
{
|
||||
return (double)getChannelBandwidth() * getOversampling();
|
||||
return getChannelBandwidth() * getOversampling();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -550,9 +550,9 @@ public class ChannelCalculator
|
||||
{
|
||||
mLog.debug("Starting ....");
|
||||
|
||||
int channelBandwidth = 12500;
|
||||
int sampleRate = 100000; //Airspy
|
||||
int channelCount = sampleRate / channelBandwidth;
|
||||
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);
|
||||
|
||||
@@ -59,7 +59,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
|
||||
if(channelCount % 2 != 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Channel count must be an even multiple of the oversample rate (2)");
|
||||
throw new IllegalArgumentException("Channel count must be an even multiple of the oversample rate (2x)");
|
||||
}
|
||||
|
||||
init(taps);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
* Copyright (C) 2014-2018 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
|
||||
@@ -19,26 +19,31 @@ import io.github.dsheirer.dsp.filter.channelizer.output.IPolyphaseChannelOutputP
|
||||
import io.github.dsheirer.dsp.filter.channelizer.output.OneChannelOutputProcessor;
|
||||
import io.github.dsheirer.dsp.filter.channelizer.output.TwoChannelOutputProcessor;
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.IComplexBufferProvider;
|
||||
import io.github.dsheirer.source.Source;
|
||||
import io.github.dsheirer.source.ISourceEventProcessor;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.TunerController;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public class PolyphaseChannelManager implements Listener<SourceEvent>
|
||||
public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
|
||||
private static final int CHANNEL_BANDWIDTH = 12500;
|
||||
private static final double MINIMUM_CHANNEL_BANDWIDTH = 12500.0;
|
||||
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 Broadcaster<SourceEvent> mSourceEventBroadcaster = new Broadcaster<>();
|
||||
private IComplexBufferProvider mComplexBufferProvider;
|
||||
private List<PolyphaseChannelSource> mChannelSources = new CopyOnWriteArrayList<>();
|
||||
private ChannelCalculator mChannelCalculator;
|
||||
@@ -61,7 +66,7 @@ public class PolyphaseChannelManager implements Listener<SourceEvent>
|
||||
* @param frequency of the provided complex buffer samples
|
||||
* @param sampleRate of the provided complex buffer samples
|
||||
*/
|
||||
public PolyphaseChannelManager(IComplexBufferProvider complexBufferProvider, long frequency, int sampleRate)
|
||||
public PolyphaseChannelManager(IComplexBufferProvider complexBufferProvider, long frequency, double sampleRate)
|
||||
{
|
||||
if(complexBufferProvider == null)
|
||||
{
|
||||
@@ -70,14 +75,13 @@ public class PolyphaseChannelManager implements Listener<SourceEvent>
|
||||
|
||||
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 = (int)(sampleRate / MINIMUM_CHANNEL_BANDWIDTH);
|
||||
|
||||
int channelCount = sampleRate / CHANNEL_BANDWIDTH;
|
||||
//Ensure channel count is an even integer since we're using a 2x oversampling polyphase channelizer
|
||||
if(channelCount % 2 != 0)
|
||||
{
|
||||
channelCount--;
|
||||
}
|
||||
|
||||
mChannelCalculator = new ChannelCalculator(sampleRate, channelCount, frequency, CHANNEL_OVERSAMPLING);
|
||||
|
||||
@@ -86,13 +90,37 @@ public class PolyphaseChannelManager implements Listener<SourceEvent>
|
||||
updateChannelizer(sampleRate, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a polyphase channel manager for the tuner controller
|
||||
*/
|
||||
public PolyphaseChannelManager(TunerController tunerController)
|
||||
{
|
||||
this(tunerController, tunerController.getFrequency(), tunerController.getSampleRate());
|
||||
}
|
||||
|
||||
/**
|
||||
* Current channel sample rate which is (2 * channel bandwidth).
|
||||
*/
|
||||
public double getChannelSampleRate()
|
||||
{
|
||||
return mChannelCalculator.getChannelSampleRate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Current channel bandwidth/spacing.
|
||||
*/
|
||||
public double getChannelBandwidth()
|
||||
{
|
||||
return mChannelCalculator.getChannelBandwidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
public TunerChannelSource getChannel(TunerChannel tunerChannel)
|
||||
{
|
||||
PolyphaseChannelSource channelSource = null;
|
||||
|
||||
@@ -105,7 +133,7 @@ public class PolyphaseChannelManager implements Listener<SourceEvent>
|
||||
if(outputProcessor != null)
|
||||
{
|
||||
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
|
||||
CHANNEL_SAMPLE_RATE);
|
||||
mChannelCalculator.getChannelSampleRate());
|
||||
|
||||
mChannelSources.add(channelSource);
|
||||
}
|
||||
@@ -131,9 +159,9 @@ public class PolyphaseChannelManager implements Listener<SourceEvent>
|
||||
switch(indexes.size())
|
||||
{
|
||||
case 1:
|
||||
return new OneChannelOutputProcessor(CHANNEL_SAMPLE_RATE, indexes);
|
||||
return new OneChannelOutputProcessor(mChannelCalculator.getChannelSampleRate(), indexes);
|
||||
case 2:
|
||||
return new TwoChannelOutputProcessor(CHANNEL_SAMPLE_RATE, indexes);
|
||||
return new TwoChannelOutputProcessor(mChannelCalculator.getChannelSampleRate(), 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());
|
||||
@@ -152,6 +180,7 @@ public class PolyphaseChannelManager implements Listener<SourceEvent>
|
||||
synchronized(mBufferProcessor)
|
||||
{
|
||||
mPolyphaseChannelizer.addChannel(channelSource);
|
||||
mSourceEventBroadcaster.broadcast(SourceEvent.channelCountChange(mChannelSources.size()));
|
||||
|
||||
if(mPolyphaseChannelizer.getRegisteredChannelCount() == 1)
|
||||
{
|
||||
@@ -172,6 +201,7 @@ public class PolyphaseChannelManager implements Listener<SourceEvent>
|
||||
synchronized(mBufferProcessor)
|
||||
{
|
||||
mChannelSources.remove(channelSource);
|
||||
mSourceEventBroadcaster.broadcast(SourceEvent.channelCountChange(mChannelSources.size()));
|
||||
|
||||
if(mPolyphaseChannelizer.getRegisteredChannelCount() == 0)
|
||||
{
|
||||
@@ -182,11 +212,11 @@ public class PolyphaseChannelManager implements Listener<SourceEvent>
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the Listener<SourceEvent> interface for receiving notifications of frequency and sample rate
|
||||
* Implements the ISourceEventProcessor interface for receiving notifications of frequency and sample rate
|
||||
* changes from the tuner.
|
||||
*/
|
||||
@Override
|
||||
public void receive(SourceEvent sourceEvent)
|
||||
public void process(SourceEvent sourceEvent)
|
||||
{
|
||||
switch(sourceEvent.getEvent())
|
||||
{
|
||||
@@ -199,6 +229,9 @@ public class PolyphaseChannelManager implements Listener<SourceEvent>
|
||||
//Update the channelizer (and the output processors) for the new sample rate
|
||||
updateChannelizer(sourceEvent.getValue().intValue(), sourceEvent);
|
||||
break;
|
||||
default:
|
||||
mLog.info("Received an unrecognized source event: " + sourceEvent.getEvent());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +243,7 @@ public class PolyphaseChannelManager implements Listener<SourceEvent>
|
||||
* 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)
|
||||
private void updateChannelizer(double sampleRate, SourceEvent sourceEvent)
|
||||
{
|
||||
mChannelCalculator.setSampleRate(sampleRate);
|
||||
|
||||
@@ -322,6 +355,46 @@ public class PolyphaseChannelManager implements Listener<SourceEvent>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorted set of currently sourced tuner channels being provided by this channel manager. The set is ordered by
|
||||
* frequency (low to high).
|
||||
*/
|
||||
public SortedSet<TunerChannel> getTunerChannels()
|
||||
{
|
||||
SortedSet<TunerChannel> tunerChannels = new TreeSet<>();
|
||||
|
||||
for(PolyphaseChannelSource channelSource: mChannelSources)
|
||||
{
|
||||
tunerChannels.add(channelSource.getTunerChannel());
|
||||
}
|
||||
|
||||
return tunerChannels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count of currently sourced tuner channels
|
||||
*/
|
||||
public int getTunerChannelCount()
|
||||
{
|
||||
return mChannelSources.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the listener to receive source events
|
||||
*/
|
||||
public void addSourceEventListener(Listener<SourceEvent> listener)
|
||||
{
|
||||
mSourceEventBroadcaster.addListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving source events
|
||||
*/
|
||||
public void removeSourceEventListener(Listener<SourceEvent> listener)
|
||||
{
|
||||
mSourceEventBroadcaster.removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal class for handling requests for start/stop sample stream from polyphase channel sources
|
||||
*/
|
||||
|
||||
@@ -38,7 +38,7 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
|
||||
* @param sampleRate of the output sample stream.
|
||||
* @param channelIndexes containing two channel indices.
|
||||
*/
|
||||
public TwoChannelOutputProcessor(int sampleRate, List<Integer> channelIndexes)
|
||||
public TwoChannelOutputProcessor(double 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
|
||||
|
||||
@@ -18,15 +18,17 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.source;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
public class SourceEvent
|
||||
{
|
||||
public enum Event
|
||||
{
|
||||
NOTIFICATION_CHANNEL_COUNT_CHANGE,
|
||||
NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE,
|
||||
NOTIFICATION_CHANNEL_SAMPLE_RATE_CHANGE,
|
||||
NOTIFICATION_FREQUENCY_CHANGE,
|
||||
NOTIFICATION_FREQUENCY_CORRECTION_CHANGE,
|
||||
NOTIFICATION_POLYPHASE_CHANNEL_COUNT_CHANGE,
|
||||
NOTIFICATION_SAMPLE_RATE_CHANGE,
|
||||
|
||||
REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE,
|
||||
@@ -34,6 +36,11 @@ public class SourceEvent
|
||||
REQUEST_SOURCE_DISPOSE,
|
||||
REQUEST_START_SAMPLE_STREAM,
|
||||
REQUEST_STOP_SAMPLE_STREAM;
|
||||
|
||||
public static EnumSet<Event> NOTIFICATION_EVENTS =
|
||||
EnumSet.range(NOTIFICATION_CHANNEL_COUNT_CHANGE, NOTIFICATION_SAMPLE_RATE_CHANGE);
|
||||
public static EnumSet<Event> REQUEST_EVENTS =
|
||||
EnumSet.range(NOTIFICATION_CHANNEL_COUNT_CHANGE, NOTIFICATION_SAMPLE_RATE_CHANGE);
|
||||
}
|
||||
|
||||
private Event mEvent;
|
||||
@@ -66,6 +73,22 @@ public class SourceEvent
|
||||
return mEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this is a notification event
|
||||
*/
|
||||
public boolean isNotificationEvent()
|
||||
{
|
||||
return Event.NOTIFICATION_EVENTS.contains(mEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this is a request event
|
||||
*/
|
||||
public boolean isRequestEvent()
|
||||
{
|
||||
return Event.REQUEST_EVENTS.contains(mEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Value associated with the event.
|
||||
*/
|
||||
@@ -152,7 +175,7 @@ public class SourceEvent
|
||||
*/
|
||||
public static SourceEvent channelCountChange(int channelCount)
|
||||
{
|
||||
return new SourceEvent(Event.NOTIFICATION_POLYPHASE_CHANNEL_COUNT_CHANGE, channelCount);
|
||||
return new SourceEvent(Event.NOTIFICATION_CHANNEL_COUNT_CHANGE, channelCount);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -199,4 +222,9 @@ public class SourceEvent
|
||||
{
|
||||
return new SourceEvent(Event.REQUEST_SOURCE_DISPOSE, source);
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return "SOURCE EVENT:" + mEvent + " VALUE:" + (mValue != null ? mValue : "(empty)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2016 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/>
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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 io.github.dsheirer.source.tuner;
|
||||
|
||||
@@ -24,20 +22,17 @@ import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.tuner.TunerEvent.Event;
|
||||
import io.github.dsheirer.source.tuner.manager.AbstractSourceManager;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerSourceManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Tuner - provides tuner channel sources, representing a channel frequency
|
||||
* Tuner provides an interface to a software or hardware tuner controller that provides I/Q sample data coupled with an
|
||||
* abstract source manager to provide access to Digital Drop Channel (DDC) resources.
|
||||
*/
|
||||
public abstract class Tuner
|
||||
public abstract class Tuner implements ISourceEventProcessor
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(Tuner.class);
|
||||
|
||||
protected Broadcaster<TunerEvent> mTunerEventBroadcaster = new Broadcaster<>();
|
||||
private Broadcaster<TunerEvent> mTunerEventBroadcaster = new Broadcaster<>();
|
||||
private TunerController mTunerController;
|
||||
private AbstractSourceManager mSourceManager;
|
||||
private String mName;
|
||||
private AbstractSourceManager mTunerSourceManager;
|
||||
|
||||
/**
|
||||
* Abstract tuner class.
|
||||
@@ -48,28 +43,31 @@ public abstract class Tuner
|
||||
{
|
||||
mName = name;
|
||||
mTunerController = tunerController;
|
||||
//Register to receive frequency and sample rate change notifications
|
||||
mTunerController.addListener(this::process);
|
||||
|
||||
mTunerSourceManager = new TunerSourceManager(mTunerController);
|
||||
mSourceManager = new TunerSourceManager(mTunerController);
|
||||
//Register to receive channel count change notifications
|
||||
mSourceManager.addSourceEventListener(this::process);
|
||||
}
|
||||
|
||||
//Rebroadcast frequency and sample rate change events as tuner events
|
||||
mTunerController.addListener(new ISourceEventProcessor()
|
||||
@Override
|
||||
public void process(SourceEvent event)
|
||||
{
|
||||
switch(event.getEvent())
|
||||
{
|
||||
@Override
|
||||
public void process(SourceEvent event)
|
||||
{
|
||||
switch(event.getEvent())
|
||||
{
|
||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||
broadcast(new TunerEvent(Tuner.this, Event.FREQUENCY));
|
||||
break;
|
||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||
broadcast(new TunerEvent(Tuner.this, Event.SAMPLE_RATE));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
case NOTIFICATION_CHANNEL_COUNT_CHANGE:
|
||||
broadcast(new TunerEvent(Tuner.this, Event.CHANNEL_COUNT));
|
||||
break;
|
||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||
broadcast(new TunerEvent(Tuner.this, Event.FREQUENCY));
|
||||
break;
|
||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||
broadcast(new TunerEvent(Tuner.this, Event.SAMPLE_RATE));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,14 +75,20 @@ public abstract class Tuner
|
||||
*/
|
||||
public AbstractSourceManager getSourceManager()
|
||||
{
|
||||
return mTunerSourceManager;
|
||||
return mSourceManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tuner controller.
|
||||
*/
|
||||
public TunerController getTunerController()
|
||||
{
|
||||
return mTunerController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Name for this tuner
|
||||
*/
|
||||
public String toString()
|
||||
{
|
||||
return mName;
|
||||
@@ -94,19 +98,19 @@ public abstract class Tuner
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name for this tuner
|
||||
*/
|
||||
public void setName(String name)
|
||||
{
|
||||
mName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unique identifier for this tuner, used to lookup a tuner configuration
|
||||
* from the settings manager.
|
||||
* Unique identifier for this tuner, used to lookup a tuner configuration from the settings manager.
|
||||
*
|
||||
* @return - unique identifier like a serial number, or a usb bus location
|
||||
* or ip address and port. Return some form of unique identification that
|
||||
* allows this tuner to be identified from among several of the same types
|
||||
* of tuners.
|
||||
* @return - unique identifier like a serial number, or a usb bus location or ip address and port. Return some
|
||||
* form of unique identification that allows this tuner to be identified from among the same types of tuners.
|
||||
*/
|
||||
public abstract String getUniqueID();
|
||||
|
||||
@@ -154,7 +158,7 @@ public abstract class Tuner
|
||||
/**
|
||||
* Broadcasts the tuner change event
|
||||
*/
|
||||
public void broadcast(TunerEvent tunerEvent)
|
||||
protected void broadcast(TunerEvent tunerEvent)
|
||||
{
|
||||
mTunerEventBroadcaster.broadcast(tunerEvent);
|
||||
}
|
||||
|
||||
@@ -26,29 +26,19 @@ import io.github.dsheirer.source.ISourceEventProcessor;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.SourceEventListenerToProcessorAdapter;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.channel.CICTunerChannelSource;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.frequency.FrequencyController;
|
||||
import io.github.dsheirer.source.tuner.frequency.FrequencyController.Tunable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
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>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(TunerController.class);
|
||||
protected Broadcaster<ComplexBuffer> mSampleBroadcaster = new Broadcaster<>();
|
||||
|
||||
/* List of currently tuned channels being served to demod channels */
|
||||
private SortedSet<TunerChannel> mTunedChannels = new ConcurrentSkipListSet<>();
|
||||
protected FrequencyController mFrequencyController;
|
||||
private int mMiddleUnusable;
|
||||
private int mMiddleUnusableHalfBandwidth;
|
||||
private double mUsableBandwidthPercentage;
|
||||
private Listener<SourceEvent> mSourceEventListener;
|
||||
|
||||
@@ -58,15 +48,15 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
|
||||
*
|
||||
* @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 middleUnusableHalfBandwidth 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)
|
||||
public TunerController(long minimumFrequency, long maximumFrequency, int middleUnusableHalfBandwidth, double usableBandwidth)
|
||||
{
|
||||
mFrequencyController = new FrequencyController(this, minimumFrequency, maximumFrequency, 0.0d);
|
||||
mMiddleUnusable = middleUnusable;
|
||||
mMiddleUnusableHalfBandwidth = middleUnusableHalfBandwidth;
|
||||
mUsableBandwidthPercentage = usableBandwidth;
|
||||
mSourceEventListener = new SourceEventListenerToProcessorAdapter(this);
|
||||
}
|
||||
@@ -182,218 +172,39 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if channel along with all of the other currently sourced
|
||||
* channels can fit within the tunable bandwidth.
|
||||
* Total bandwidth of the middle unusable bandwidth region. This value is used to avoid a central DC spike
|
||||
* present in some tuners.
|
||||
*/
|
||||
private boolean canTune(TunerChannel channel)
|
||||
public int getMiddleUnusableHalfBandwidth()
|
||||
{
|
||||
//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;
|
||||
return mMiddleUnusableHalfBandwidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Indicates if this tuner controller has a middle unusable bandwidth region.
|
||||
*/
|
||||
public TunerChannelSource getChannel(Tuner tuner, TunerChannel channel) throws RejectedExecutionException
|
||||
public boolean hasMiddleUnusableBandwidth()
|
||||
{
|
||||
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");
|
||||
}
|
||||
return mMiddleUnusableHalfBandwidth != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Usable bandwidth - total bandwidth minus the unusable space at either end of the spectrum.
|
||||
*/
|
||||
private int getUsableBandwidth()
|
||||
public int getUsableBandwidth()
|
||||
{
|
||||
return (int)(getBandwidth() * mUsableBandwidthPercentage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Usable half bandwidth - total bandwidth minus unusable space at either end of the spectrum, minus the blocked
|
||||
* center bandwidth.
|
||||
*/
|
||||
public int getUsableHalfBandwidth()
|
||||
{
|
||||
return (int)(getUsableBandwidth() / 2) - mMiddleUnusableHalfBandwidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the listener to be notified any time that the tuner changes frequency
|
||||
* or bandwidth/sample rate.
|
||||
|
||||
@@ -235,7 +235,7 @@ public class TunerModel extends AbstractTableModel implements Listener<TunerEven
|
||||
return 0;
|
||||
}
|
||||
case CHANNEL_COUNT:
|
||||
return tuner.getTunerController().getChannelCount();
|
||||
return tuner.getSourceManager().getTunerChannelCount();
|
||||
case SPECTRAL_DISPLAY_MAIN:
|
||||
return "Main";
|
||||
case SPECTRAL_DISPLAY_NEW:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
* Copyright (C) 2014-2018 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
|
||||
@@ -19,58 +19,44 @@ import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||
import io.github.dsheirer.sample.complex.IComplexBufferProvider;
|
||||
import io.github.dsheirer.source.ISourceEventProcessor;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.SortedSet;
|
||||
|
||||
/**
|
||||
* 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>
|
||||
public abstract class AbstractSourceManager implements IComplexBufferProvider, ISourceEventProcessor
|
||||
{
|
||||
protected Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>();
|
||||
private Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>();
|
||||
private Broadcaster<SourceEvent> mSourceEventBroadcaster = new Broadcaster<>();
|
||||
|
||||
/**
|
||||
* Indicates if this source manager can tune (ie provide a source for) the specified tuner channel
|
||||
* Sorted set of tuner channels being sourced by this source manager. Set is ordered by frequency lowest to highest
|
||||
*/
|
||||
public abstract boolean canTune(TunerChannel tunerChannel);
|
||||
public abstract SortedSet<TunerChannel> getTunerChannels();
|
||||
|
||||
/**
|
||||
* List of tuner channels being sourced by this source manager
|
||||
* Count of tuner channels being sourced by this source manager.
|
||||
* @return
|
||||
*/
|
||||
public abstract List<TunerChannel> getChannels();
|
||||
public abstract int getTunerChannelCount();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* Note: you MUST invoke start() on the obtained source to start the sample flow and invoke stop() to release all
|
||||
* resources allocated for the tuner channel source.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
@@ -105,4 +91,28 @@ public abstract class AbstractSourceManager implements IComplexBufferProvider, L
|
||||
{
|
||||
mComplexBufferBroadcaster.broadcast(complexBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to receive source events
|
||||
*/
|
||||
public void addSourceEventListener(Listener<SourceEvent> listener)
|
||||
{
|
||||
mSourceEventBroadcaster.addListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the listener from receiving source events
|
||||
*/
|
||||
public void removeSourceEventListener(Listener<SourceEvent> listener)
|
||||
{
|
||||
mSourceEventBroadcaster.removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the source event to any registered listeners
|
||||
*/
|
||||
protected void broadcast(SourceEvent sourceEvent)
|
||||
{
|
||||
mSourceEventBroadcaster.broadcast(sourceEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
* Copyright (C) 2014-2018 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
|
||||
@@ -16,65 +16,405 @@
|
||||
package io.github.dsheirer.source.tuner.manager;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.channelizer.PolyphaseChannelManager;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.TunerController;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.SortedSet;
|
||||
|
||||
public class TunerSourceManager extends AbstractSourceManager
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(TunerSourceManager.class);
|
||||
private PolyphaseChannelManager mPolyphaseChannelManager;
|
||||
private TunerController mTunerController;
|
||||
|
||||
/**
|
||||
* TunerSourceManager is responsible for managing the tuner's center tuned frequency and providing access to
|
||||
* polyphase tuner channel sources (ie DDCs). This class is responsible for determining IF a requested channel can
|
||||
* be provided and then adjusting the center frequency and provisioning a DDC Polyphase Tuner channel source.
|
||||
*
|
||||
* @param tunerController with a center tuned frequency that will be managed by this instance
|
||||
*/
|
||||
public TunerSourceManager(TunerController tunerController)
|
||||
{
|
||||
mTunerController = tunerController;
|
||||
//Register to receive frequency and sample rate notifications
|
||||
mTunerController.addListener(this);
|
||||
|
||||
mPolyphaseChannelManager = new PolyphaseChannelManager(tunerController);
|
||||
//Register to receive channel count change notifications for rebroadcasting
|
||||
mPolyphaseChannelManager.addSourceEventListener(this::process);
|
||||
}
|
||||
|
||||
//TODO: broadcast a tuner event each time we add a new channel to the polyphase manager
|
||||
|
||||
@Override
|
||||
public boolean canTune(TunerChannel tunerChannel)
|
||||
/**
|
||||
* Indicates if the channel min/max frequencies are within the tunable frequency range of the tuner controller
|
||||
*
|
||||
* @param tunerChannel to check
|
||||
* @return true if the channel is within the frequency range of the tuner controller
|
||||
*/
|
||||
private boolean isTunable(TunerChannel tunerChannel)
|
||||
{
|
||||
return mTunerController.canTune(tunerChannel.getMinFrequency()) &&
|
||||
mTunerController.canTune(tunerChannel.getMaxFrequency());
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the list of tuner channels can be tuned by the tuner controller within the current bandwidth and
|
||||
* tuner center-blocked frequency range limitations.
|
||||
*
|
||||
* @param tunerChannels that are being evaluated
|
||||
* @return true if the set of channels fit within the bandwidth and center blocked frequency band of the tuner
|
||||
*/
|
||||
private boolean canTune(SortedSet<TunerChannel> tunerChannels)
|
||||
{
|
||||
//If there is only one channel, check to ensure it fits the current bandwidth or half-bandwidth
|
||||
if(tunerChannels.size() == 1)
|
||||
{
|
||||
if(mTunerController.hasMiddleUnusableBandwidth())
|
||||
{
|
||||
return tunerChannels.first().getBandwidth() < mTunerController.getUsableHalfBandwidth();
|
||||
}
|
||||
else
|
||||
{
|
||||
return tunerChannels.first().getBandwidth() < mTunerController.getUsableBandwidth();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Check that the total bandwidth of the channel set is within the usable bandwidth of the tuner
|
||||
int tunerUsableBandwidth = mTunerController.getUsableBandwidth();
|
||||
|
||||
long channelSetBandwidth = tunerChannels.last().getMaxFrequency() - tunerChannels.first().getMinFrequency();
|
||||
|
||||
if(channelSetBandwidth <= tunerUsableBandwidth)
|
||||
{
|
||||
//If the tuner doesn't have a center DC spike blocked region, we're good
|
||||
if(!mTunerController.hasMiddleUnusableBandwidth())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//Check to see if the channel set fits within the usable half bandwidth of the tuner
|
||||
int tunerUsableHalfBandwidth = mTunerController.getUsableHalfBandwidth();
|
||||
|
||||
if(channelSetBandwidth < tunerUsableHalfBandwidth)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//At this point we're within the tuner's total usable bandwidth, but there is a central DC spike blocked
|
||||
//region. See if we can notionally partition the channels into left/right usable half bandwidth regions
|
||||
TunerChannel firstRightChannel = null;
|
||||
|
||||
for(TunerChannel tunerChannel: tunerChannels)
|
||||
{
|
||||
if(firstRightChannel == null)
|
||||
{
|
||||
long leftSideBandwidth = tunerChannel.getMaxFrequency() - tunerChannels.first().getMinFrequency();
|
||||
|
||||
if(leftSideBandwidth > tunerUsableHalfBandwidth)
|
||||
{
|
||||
//Ensure this first right-partition channel fits within the half bandwidth
|
||||
if(tunerChannel.getBandwidth() > tunerUsableHalfBandwidth)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
firstRightChannel = tunerChannel;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
long rightSideBandwidth = tunerChannel.getMaxFrequency() - firstRightChannel.getMinFrequency();
|
||||
|
||||
//If the right side partition's bandwidth exceeds the half bandwidth, we're done
|
||||
if(rightSideBandwidth > tunerUsableHalfBandwidth)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//All channels can be partitioned into notional left/right partition sets - we're good
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TunerChannel> getChannels()
|
||||
/**
|
||||
* Calculates the optimal center tune frequency for the set of currently sourced tuner channels. Note: the returned
|
||||
* frequency will only be different from the current center frequency argument if a change is required in order
|
||||
* to fit all of the sourced tuner channels, or to better optimize the channels for alignment with the current
|
||||
* polyphase channelizer channel partitioning.
|
||||
*
|
||||
* @param channels that are currently sourced by this source manager
|
||||
* @param currentCenterFrequency of the tuner.
|
||||
* @return optimal center tuned frequency for the set of currently sourced tuner channels.
|
||||
* @throws IllegalArgumentException if a center frequency cannot be determined for the set of tuner channels
|
||||
*/
|
||||
private long getCenterFrequency(SortedSet<TunerChannel> channels, long currentCenterFrequency)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
return null;
|
||||
if(channels.isEmpty())
|
||||
{
|
||||
return currentCenterFrequency;
|
||||
}
|
||||
|
||||
long bestIntegralFrequency = getIntegralFrequency(channels);
|
||||
|
||||
//See if we can reuse the current frequency
|
||||
if(isIntegralSpacing(currentCenterFrequency, bestIntegralFrequency) &&
|
||||
isValidCenterFrequency(channels, currentCenterFrequency))
|
||||
{
|
||||
return currentCenterFrequency;
|
||||
}
|
||||
|
||||
int integralHalfChannelCount = (int)(mTunerController.getUsableHalfBandwidth() /
|
||||
mPolyphaseChannelManager.getChannelBandwidth()) + 1;
|
||||
|
||||
int integralHalfBandwidth = (int)(integralHalfChannelCount * mPolyphaseChannelManager.getChannelBandwidth());
|
||||
|
||||
long minimumFrequency = channels.last().getFrequency() - integralHalfBandwidth;
|
||||
long testFrequency = channels.first().getFrequency() + integralHalfBandwidth;
|
||||
|
||||
if(isValidCenterFrequency(channels,testFrequency))
|
||||
{
|
||||
return testFrequency;
|
||||
}
|
||||
|
||||
while(testFrequency >= minimumFrequency)
|
||||
{
|
||||
testFrequency -= (long)mPolyphaseChannelManager.getChannelBandwidth();
|
||||
|
||||
if(isValidCenterFrequency(channels, testFrequency))
|
||||
{
|
||||
return testFrequency;
|
||||
}
|
||||
}
|
||||
|
||||
//If we can't find an integral center frequency, choose a frequency that will fit the channels
|
||||
minimumFrequency = channels.last().getFrequency() - mTunerController.getUsableHalfBandwidth();
|
||||
testFrequency = channels.first().getMinFrequency() + mTunerController.getUsableHalfBandwidth();
|
||||
|
||||
if(isValidCenterFrequency(channels,testFrequency))
|
||||
{
|
||||
return testFrequency;
|
||||
}
|
||||
|
||||
while(testFrequency >= minimumFrequency)
|
||||
{
|
||||
testFrequency--;
|
||||
|
||||
if(isValidCenterFrequency(channels, testFrequency))
|
||||
{
|
||||
return testFrequency;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Can't calculate valid center frequency for the channel set");
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the candidate center frequency is valid to ensure that all of the channels fit within the tuner's
|
||||
* bandwidth and avoid any center DC spike blocked frequency region
|
||||
*
|
||||
* @param tunerChannels to test for the candidate center frequency
|
||||
* @param centerFrequency to test
|
||||
* @return true if the candidate center frequency is valid for the set of channels
|
||||
*/
|
||||
private boolean isValidCenterFrequency(SortedSet<TunerChannel> tunerChannels, long centerFrequency)
|
||||
{
|
||||
for(TunerChannel tunerChannel: tunerChannels)
|
||||
{
|
||||
if(!isValidCenterFrequency(tunerChannel, centerFrequency))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the channel will fit within the tuner's bandwidth if the tuner were using the candidate center
|
||||
* frequency and that the channel does not overlap an unusable center region that would be centered about the
|
||||
* candidate center frequency
|
||||
*
|
||||
* @param tunerChannel to test for fit
|
||||
* @param centerFrequency to test against
|
||||
* @return true if the channel will fit when the tuner uses the specified center frequency
|
||||
*/
|
||||
private boolean isValidCenterFrequency(TunerChannel tunerChannel, long centerFrequency)
|
||||
{
|
||||
boolean fits = (tunerChannel.getMinFrequency() >= centerFrequency - mTunerController.getUsableHalfBandwidth()) &&
|
||||
(tunerChannel.getMaxFrequency() <= centerFrequency + mTunerController.getUsableHalfBandwidth());
|
||||
|
||||
if(fits && mTunerController.hasMiddleUnusableBandwidth())
|
||||
{
|
||||
fits = !tunerChannel.overlaps(centerFrequency - mTunerController.getMiddleUnusableHalfBandwidth(),
|
||||
centerFrequency + mTunerController.getMiddleUnusableHalfBandwidth());
|
||||
}
|
||||
|
||||
return fits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the two frequencies have a spacing that is an integral multiple of the channel bandwidth.
|
||||
*/
|
||||
private boolean isIntegralSpacing(long frequencyA, long frequencyB)
|
||||
{
|
||||
double delta = Math.abs(frequencyA - frequencyB);
|
||||
return delta % mPolyphaseChannelManager.getChannelBandwidth() <= 1.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies the tuner channel frequency that has the best fit as an integral multiple of all of the other tuner
|
||||
* channels. Using a polyphase channelizer to split each of the channels at a spacing of the channel sample rate,
|
||||
* best fit is defined as the lowest absolute miss distance for each channel from the integral centers
|
||||
* for each of the channels in the polyphase channelizer.
|
||||
*
|
||||
* @param channels containing a subset of channels that are spaced at integral multiples of the channel sample rate
|
||||
* @return best channel containing the highest subset of matching, integrally spaced channels in the set, or null
|
||||
* if there are no channels that are integrally spaced.
|
||||
*/
|
||||
private long getIntegralFrequency(SortedSet<TunerChannel> channels)
|
||||
{
|
||||
if(channels.isEmpty())
|
||||
{
|
||||
throw new IllegalArgumentException("Channels cannot be empty");
|
||||
}
|
||||
|
||||
long bestIntegralFrequency = channels.first().getFrequency();
|
||||
|
||||
if(channels.size() > 1)
|
||||
{
|
||||
double bestScore = Double.MAX_VALUE;
|
||||
|
||||
for(TunerChannel firstChannel: channels)
|
||||
{
|
||||
double score = 0.0;
|
||||
|
||||
for(TunerChannel secondChannel: channels)
|
||||
{
|
||||
if(firstChannel != secondChannel)
|
||||
{
|
||||
double delta = Math.abs(firstChannel.getFrequency() - secondChannel.getFrequency()) %
|
||||
mPolyphaseChannelManager.getChannelBandwidth();
|
||||
|
||||
score += delta;
|
||||
}
|
||||
}
|
||||
|
||||
if(score < bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
bestIntegralFrequency = firstChannel.getFrequency();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bestIntegralFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of tuner channels currently being sourced by this source manager
|
||||
*/
|
||||
@Override
|
||||
public SortedSet<TunerChannel> getTunerChannels()
|
||||
{
|
||||
return mPolyphaseChannelManager.getTunerChannels();
|
||||
}
|
||||
|
||||
/**
|
||||
* Count of tuner channels currently being sourced by this source manager
|
||||
*/
|
||||
@Override
|
||||
public int getTunerChannelCount()
|
||||
{
|
||||
return mPolyphaseChannelManager.getTunerChannelCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates a tuner channel source for the tuner channel.
|
||||
*
|
||||
* @param tunerChannel for requested source
|
||||
* @return allocated DDC tuner channel source, or null if the channel cannot be provided by this source manager
|
||||
*/
|
||||
@Override
|
||||
public TunerChannelSource getSource(TunerChannel tunerChannel)
|
||||
{
|
||||
if(isTunable(tunerChannel))
|
||||
{
|
||||
//Get a new set of currently tuned channels
|
||||
SortedSet<TunerChannel> tunerChannels = getTunerChannels();
|
||||
|
||||
//Add the requested channel to the list
|
||||
tunerChannels.add(tunerChannel);
|
||||
|
||||
if(canTune(tunerChannels))
|
||||
{
|
||||
long currentCenterFrequency = mTunerController.getFrequency();
|
||||
long updatedCenterFrequency = 0;
|
||||
|
||||
//Attempt to adjust the center frequency before we allocate the channel
|
||||
try
|
||||
{
|
||||
updatedCenterFrequency = getCenterFrequency(tunerChannels, currentCenterFrequency);
|
||||
|
||||
if(updatedCenterFrequency != currentCenterFrequency && updatedCenterFrequency != 0)
|
||||
{
|
||||
mTunerController.setFrequency(updatedCenterFrequency);
|
||||
}
|
||||
|
||||
//If we're successful to here, allocate the channel
|
||||
return mPolyphaseChannelManager.getChannel(tunerChannel);
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
//Tuner controller threw an error trying to tune to the updated frequency
|
||||
mLog.error("Error while updating tuner controller with new center frequency [" +
|
||||
updatedCenterFrequency + "] - unable to allocate new tuner channel", se);
|
||||
}
|
||||
catch(IllegalArgumentException iae)
|
||||
{
|
||||
//Center frequency calculation failed
|
||||
mLog.error("Couldn't calculate new tuner center frequency for new tuner channel [" +
|
||||
tunerChannel + "] unable to allocate new tuner channel", iae);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes source events received from the tuner by simply passing them on to the embedded polyphase channel
|
||||
* manager
|
||||
*/
|
||||
@Override
|
||||
public void releaseSource(TunerChannelSource source)
|
||||
public void process(SourceEvent sourceEvent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSourceEventListener(Listener<SourceEvent> sourceEventListener)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSourceEventListener(Listener<SourceEvent> sourceEventListener)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(SourceEvent sourceEvent)
|
||||
{
|
||||
|
||||
switch(sourceEvent.getEvent())
|
||||
{
|
||||
case NOTIFICATION_CHANNEL_COUNT_CHANGE:
|
||||
//Rebroadcast this event to any registered listeners (ie tuner and tuner controller)
|
||||
broadcast(sourceEvent);
|
||||
break;
|
||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||
mPolyphaseChannelManager.process(sourceEvent);
|
||||
break;
|
||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||
mPolyphaseChannelManager.process(sourceEvent);
|
||||
break;
|
||||
default:
|
||||
mLog.info("Received an unrecognized source event: " + sourceEvent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user