mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
Creates new HeterodyneChannelSourceManager that uses frequency translation
and CIC decimation for each channel provided by the tuner.
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
/*******************************************************************************
|
||||
* 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.manager;
|
||||
|
||||
import io.github.dsheirer.source.tuner.TunerController;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* Calculates tuner center frequency for a set of tuner channels.
|
||||
*/
|
||||
public class CenterFrequencyCalculator
|
||||
{
|
||||
public static final long INVALID_FREQUENCY = -1;
|
||||
|
||||
/**
|
||||
* Determines the optimal center frequency for a given tuner and set of tuner channels.
|
||||
*
|
||||
* @param tunerController that is providing tuner channels
|
||||
* @param channels to calculate
|
||||
* @return optimal center frequency or INVALID_FREQUENCY if a center frequency cannot be calculated.
|
||||
*/
|
||||
public static long getCenterFrequency(TunerController tunerController, SortedSet<TunerChannel> channels)
|
||||
{
|
||||
long candidateFrequency = INVALID_FREQUENCY;
|
||||
|
||||
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(channels.size() == 1)
|
||||
{
|
||||
candidateFrequency = channels.first().getMinFrequency() - tunerController.getMiddleUnusableHalfBandwidth() + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
long minChannelFrequency = channels.first().getMinFrequency();
|
||||
long maxChannelFrequency = channels.last().getMaxFrequency();
|
||||
|
||||
//Start by placing the highest frequency channel at the high end of the spectrum
|
||||
candidateFrequency = maxChannelFrequency - (tunerController.getUsableBandwidth() / 2);
|
||||
|
||||
//Iterate the channels and make sure that none of them overlap the center DC spike buffer, if one exists
|
||||
if(tunerController.getMiddleUnusableHalfBandwidth() > 0)
|
||||
{
|
||||
boolean processingRequired = true;
|
||||
|
||||
while(isValidCandidateFrequency && processingRequired)
|
||||
{
|
||||
processingRequired = false;
|
||||
|
||||
long minAvoid = candidateFrequency - tunerController.getMiddleUnusableHalfBandwidth();
|
||||
long maxAvoid = candidateFrequency + tunerController.getMiddleUnusableHalfBandwidth();
|
||||
|
||||
//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 : channels)
|
||||
{
|
||||
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 - (tunerController.getUsableBandwidth() / 2) <= minChannelFrequency)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return candidateFrequency;
|
||||
}
|
||||
|
||||
return INVALID_FREQUENCY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if channel along with all of the other currently sourced
|
||||
* channels can fit within the tunable bandwidth.
|
||||
*
|
||||
*/
|
||||
/**
|
||||
*
|
||||
* Indicates if channel along with all of the other currently sourced
|
||||
* channels can fit within the tunable bandwidth.
|
||||
|
||||
* @param channel to test
|
||||
* @param tunerController that will provide the channel
|
||||
* @param channels is a current set of channels being sourced by the tuner controller
|
||||
* @return
|
||||
*/
|
||||
public static boolean canTune(TunerChannel channel, TunerController tunerController, SortedSet<TunerChannel> channels)
|
||||
{
|
||||
//Make sure we're within the tunable frequency range of this tuner
|
||||
if(tunerController.getMinFrequency() < channel.getMinFrequency() && tunerController.getMaxFrequency() > channel.getMaxFrequency())
|
||||
{
|
||||
//If this is the first lock, then we're good
|
||||
if(channels.isEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
SortedSet<TunerChannel> allChannels = new TreeSet<>(channels);
|
||||
allChannels.add(channel);
|
||||
|
||||
//If the bandwidth of the channel set is less than or equal to the tuner's usable bandwidth, then
|
||||
//check to see if we can find a valid center frequency
|
||||
if(allChannels.last().getMaxFrequency() - allChannels.first().getMinFrequency() <= tunerController.getUsableBandwidth())
|
||||
{
|
||||
return getCenterFrequency(tunerController, allChannels) != INVALID_FREQUENCY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -54,9 +54,6 @@ public abstract class ChannelSourceManager implements ISourceEventProcessor
|
||||
*/
|
||||
public abstract TunerChannelSource getSource(TunerChannel tunerChannel);
|
||||
|
||||
//TODO: remove this testing method when finished with polyphase channel manager
|
||||
public abstract TunerChannelSource getTestingSource(int index);
|
||||
|
||||
/**
|
||||
* Adds a listener to receive source events
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
/*******************************************************************************
|
||||
* 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.manager;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
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.CICTunerChannelSource2;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Channel provider for heterodyne and decimate method of channel provisioning.
|
||||
*/
|
||||
public class HeterodyneChannelSourceManager extends ChannelSourceManager
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(HeterodyneChannelSourceManager.class);
|
||||
|
||||
private final static int OBJECTIVE_CHANNEL_SAMPLE_RATE = 30000;
|
||||
|
||||
private List<CICTunerChannelSource2> mChannelSources = new CopyOnWriteArrayList<>();
|
||||
private SortedSet<TunerChannel> mTunerChannels = new TreeSet<>();
|
||||
private TunerController mTunerController;
|
||||
private ChannelSourceEventProcessor mChannelSourceEventProcessor = new ChannelSourceEventProcessor();
|
||||
|
||||
public HeterodyneChannelSourceManager(TunerController tunerController)
|
||||
{
|
||||
mTunerController = tunerController;
|
||||
mTunerController.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedSet<TunerChannel> getTunerChannels()
|
||||
{
|
||||
return mTunerChannels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTunerChannelCount()
|
||||
{
|
||||
return mTunerChannels.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunerChannelSource getSource(TunerChannel tunerChannel)
|
||||
{
|
||||
if(CenterFrequencyCalculator.canTune(tunerChannel, mTunerController, mTunerChannels))
|
||||
{
|
||||
int decimation = mTunerController.getBandwidth() / OBJECTIVE_CHANNEL_SAMPLE_RATE;
|
||||
|
||||
try
|
||||
{
|
||||
//Attempt to create the channel source first, in case we get a filter design exception
|
||||
CICTunerChannelSource2 tunerChannelSource = new CICTunerChannelSource2(mChannelSourceEventProcessor,
|
||||
tunerChannel, mTunerController.getSampleRate(), decimation);
|
||||
|
||||
//Add to the list of channel sources so that it will receive the tuner frequency change
|
||||
mChannelSources.add(tunerChannelSource);
|
||||
|
||||
//Set the current tuner frequency
|
||||
tunerChannelSource.setFrequency(mTunerController.getFrequency());
|
||||
|
||||
//Add to the channel list and update the tuner center frequency as needed
|
||||
mTunerChannels.add(tunerChannel);
|
||||
updateTunerFrequency();
|
||||
|
||||
//Lock the tuner controller frequency and sample rate
|
||||
mTunerController.setLocked(true);
|
||||
|
||||
broadcast(SourceEvent.channelCountChange(getTunerChannelCount()));
|
||||
|
||||
return tunerChannelSource;
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
{
|
||||
mLog.error("Error creating CIC tuner channel source - couldn't design cleanup filter", fde);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a new center frequency and updates the tuner center frequency
|
||||
*/
|
||||
private void updateTunerFrequency()
|
||||
{
|
||||
long centerFrequency = CenterFrequencyCalculator.getCenterFrequency(mTunerController, getTunerChannels());
|
||||
|
||||
if(centerFrequency == CenterFrequencyCalculator.INVALID_FREQUENCY)
|
||||
{
|
||||
mLog.error("Couldn't calculate center frequency for tuner and tuner channels");
|
||||
return;
|
||||
}
|
||||
|
||||
if(centerFrequency != mTunerController.getFrequency())
|
||||
{
|
||||
try
|
||||
{
|
||||
mTunerController.setFrequency(centerFrequency);
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
mLog.error("Couldn't update tuner center frequency to " + centerFrequency, se);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(SourceEvent tunerSourceEvent) throws SourceException
|
||||
{
|
||||
switch(tunerSourceEvent.getEvent())
|
||||
{
|
||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||
//Tuner center frequency has changed - update channels
|
||||
updateTunerFrequency(tunerSourceEvent.getValue().longValue());
|
||||
break;
|
||||
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
|
||||
//The tuner is self-correcting for PPM error - relay to channels
|
||||
broadcastToChannels(tunerSourceEvent);
|
||||
break;
|
||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_LOCKED:
|
||||
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_UNLOCKED:
|
||||
//no-op
|
||||
break;
|
||||
default:
|
||||
mLog.info("Unrecognized Source Event received from tuner: " + tunerSourceEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the source event to any channel
|
||||
*/
|
||||
private void broadcastToChannels(SourceEvent sourceEvent)
|
||||
{
|
||||
for(CICTunerChannelSource2 channelSource: mChannelSources)
|
||||
{
|
||||
try
|
||||
{
|
||||
channelSource.process(sourceEvent);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error broadcasting source event to channel: " + sourceEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all channel sources to use the new tuner center frequency
|
||||
*
|
||||
* @param tunerFrequency in hertz
|
||||
*/
|
||||
private void updateTunerFrequency(long tunerFrequency)
|
||||
{
|
||||
for(CICTunerChannelSource2 channelSource : mChannelSources)
|
||||
{
|
||||
channelSource.setFrequency(tunerFrequency);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes channel source events
|
||||
*/
|
||||
public class ChannelSourceEventProcessor implements Listener<SourceEvent>
|
||||
{
|
||||
@Override
|
||||
public void receive(SourceEvent sourceEvent)
|
||||
{
|
||||
switch(sourceEvent.getEvent())
|
||||
{
|
||||
case REQUEST_START_SAMPLE_STREAM:
|
||||
if(sourceEvent.getSource() instanceof CICTunerChannelSource2)
|
||||
{
|
||||
mTunerController.addBufferListener((CICTunerChannelSource2)sourceEvent.getSource());
|
||||
}
|
||||
break;
|
||||
case REQUEST_STOP_SAMPLE_STREAM:
|
||||
if(sourceEvent.getSource() instanceof CICTunerChannelSource2)
|
||||
{
|
||||
mTunerController.removeBufferListener((CICTunerChannelSource2)sourceEvent.getSource());
|
||||
}
|
||||
break;
|
||||
case REQUEST_SOURCE_DISPOSE:
|
||||
if(sourceEvent.getSource() instanceof CICTunerChannelSource2)
|
||||
{
|
||||
CICTunerChannelSource2 channelSource = (CICTunerChannelSource2)sourceEvent.getSource();
|
||||
mChannelSources.remove(channelSource);
|
||||
mTunerChannels.remove(channelSource.getTunerChannel());
|
||||
channelSource.dispose();
|
||||
|
||||
//Unlock the tuner controller if there are no more channels
|
||||
if(getTunerChannelCount() == 0)
|
||||
{
|
||||
mTunerController.setLocked(false);
|
||||
}
|
||||
broadcast(SourceEvent.channelCountChange(getTunerChannelCount()));
|
||||
}
|
||||
break;
|
||||
case NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED:
|
||||
//Rebroadcast so that the tuner source can process this event
|
||||
broadcast(sourceEvent);
|
||||
break;
|
||||
case NOTIFICATION_CHANNEL_COUNT_CHANGE:
|
||||
//Lock the tuner controller frequency & sample rate when we're processing channels
|
||||
break;
|
||||
default:
|
||||
mLog.info("Unrecognized Source Event received from channel: " + sourceEvent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,13 +49,6 @@ public class TunerChannelSourceManager extends ChannelSourceManager
|
||||
mTunerController.addListener(mPolyphaseChannelManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunerChannelSource getTestingSource(int index)
|
||||
{
|
||||
//TODO: remove this debug method
|
||||
return mPolyphaseChannelManager.getTestChannel(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the channel min/max frequencies are within the tunable frequency range of the tuner controller
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user