diff --git a/copyright_header.txt b/copyright_header.txt
deleted file mode 100644
index 3eaea8b8..00000000
--- a/copyright_header.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-/*******************************************************************************
- * 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
- ******************************************************************************/
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ChannelCalculator.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ChannelCalculator.java
index 1210e45a..8565d3ed 100644
--- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ChannelCalculator.java
+++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ChannelCalculator.java
@@ -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);
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java
index 3e0995dc..d15f0880 100644
--- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java
+++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java
@@ -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);
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java
index 2d673111..a7761217 100644
--- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java
+++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java
@@ -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
+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 mSourceEventBroadcaster = new Broadcaster<>();
private IComplexBufferProvider mComplexBufferProvider;
private List mChannelSources = new CopyOnWriteArrayList<>();
private ChannelCalculator mChannelCalculator;
@@ -61,7 +66,7 @@ public class PolyphaseChannelManager implements Listener
* @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
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
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
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
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
synchronized(mBufferProcessor)
{
mPolyphaseChannelizer.addChannel(channelSource);
+ mSourceEventBroadcaster.broadcast(SourceEvent.channelCountChange(mChannelSources.size()));
if(mPolyphaseChannelizer.getRegisteredChannelCount() == 1)
{
@@ -172,6 +201,7 @@ public class PolyphaseChannelManager implements Listener
synchronized(mBufferProcessor)
{
mChannelSources.remove(channelSource);
+ mSourceEventBroadcaster.broadcast(SourceEvent.channelCountChange(mChannelSources.size()));
if(mPolyphaseChannelizer.getRegisteredChannelCount() == 0)
{
@@ -182,11 +212,11 @@ public class PolyphaseChannelManager implements Listener
}
/**
- * Implements the Listener 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
//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
* 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
}
}
+ /**
+ * Sorted set of currently sourced tuner channels being provided by this channel manager. The set is ordered by
+ * frequency (low to high).
+ */
+ public SortedSet getTunerChannels()
+ {
+ SortedSet 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 listener)
+ {
+ mSourceEventBroadcaster.addListener(listener);
+ }
+
+ /**
+ * Removes the listener from receiving source events
+ */
+ public void removeSourceEventListener(Listener listener)
+ {
+ mSourceEventBroadcaster.removeListener(listener);
+ }
+
/**
* Internal class for handling requests for start/stop sample stream from polyphase channel sources
*/
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java
index 350567a3..23c3e120 100644
--- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java
+++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java
@@ -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 channelIndexes)
+ public TwoChannelOutputProcessor(double sampleRate, List channelIndexes)
{
//Set the frequency correction oscillator to 2 x output sample rate since we'll be correcting the frequency
//after synthesizing both input channels
diff --git a/src/main/java/io/github/dsheirer/source/SourceEvent.java b/src/main/java/io/github/dsheirer/source/SourceEvent.java
index 8f9b2857..53ef609f 100644
--- a/src/main/java/io/github/dsheirer/source/SourceEvent.java
+++ b/src/main/java/io/github/dsheirer/source/SourceEvent.java
@@ -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 NOTIFICATION_EVENTS =
+ EnumSet.range(NOTIFICATION_CHANNEL_COUNT_CHANGE, NOTIFICATION_SAMPLE_RATE_CHANGE);
+ public static EnumSet 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)");
+ }
}
diff --git a/src/main/java/io/github/dsheirer/source/tuner/Tuner.java b/src/main/java/io/github/dsheirer/source/tuner/Tuner.java
index 70e02d71..d9a19e90 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/Tuner.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/Tuner.java
@@ -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
+ * 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
+ *
******************************************************************************/
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 mTunerEventBroadcaster = new Broadcaster<>();
+ private Broadcaster 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);
}
diff --git a/src/main/java/io/github/dsheirer/source/tuner/TunerController.java b/src/main/java/io/github/dsheirer/source/tuner/TunerController.java
index 0076862f..688ee442 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/TunerController.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/TunerController.java
@@ -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
{
private final static Logger mLog = LoggerFactory.getLogger(TunerController.class);
protected Broadcaster mSampleBroadcaster = new Broadcaster<>();
-
- /* List of currently tuned channels being served to demod channels */
- private SortedSet mTunedChannels = new ConcurrentSkipListSet<>();
protected FrequencyController mFrequencyController;
- private int mMiddleUnusable;
+ private int mMiddleUnusableHalfBandwidth;
private double mUsableBandwidthPercentage;
private Listener 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.
diff --git a/src/main/java/io/github/dsheirer/source/tuner/TunerModel.java b/src/main/java/io/github/dsheirer/source/tuner/TunerModel.java
index a0735a24..919f32d6 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/TunerModel.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/TunerModel.java
@@ -235,7 +235,7 @@ public class TunerModel extends AbstractTableModel implements Listener
+public abstract class AbstractSourceManager implements IComplexBufferProvider, ISourceEventProcessor
{
- protected Broadcaster mComplexBufferBroadcaster = new Broadcaster<>();
+ private Broadcaster mComplexBufferBroadcaster = new Broadcaster<>();
+ private Broadcaster 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 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 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 sourceEventListener);
-
- /**
- * Removes the listener from receiving source events from this source manager
- * @param sourceEventListener to remove
- */
- public abstract void removeSourceEventListener(Listener 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 listener)
+ {
+ mSourceEventBroadcaster.addListener(listener);
+ }
+
+ /**
+ * Remove the listener from receiving source events
+ */
+ public void removeSourceEventListener(Listener listener)
+ {
+ mSourceEventBroadcaster.removeListener(listener);
+ }
+
+ /**
+ * Broadcasts the source event to any registered listeners
+ */
+ protected void broadcast(SourceEvent sourceEvent)
+ {
+ mSourceEventBroadcaster.broadcast(sourceEvent);
+ }
}
diff --git a/src/main/java/io/github/dsheirer/source/tuner/manager/TunerSourceManager.java b/src/main/java/io/github/dsheirer/source/tuner/manager/TunerSourceManager.java
index 8b7c913d..9276f649 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/manager/TunerSourceManager.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/manager/TunerSourceManager.java
@@ -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 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 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 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 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 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 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 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 sourceEventListener)
- {
-
- }
-
- @Override
- public void removeSourceEventListener(Listener 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;
+ }
}
}