mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
wip-channelizer
This commit is contained in:
@@ -92,9 +92,11 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener<Co
|
||||
*/
|
||||
protected void dispatch(float[] channels)
|
||||
{
|
||||
ComplexBuffer complexChannelsBuffer = new ComplexBuffer(channels);
|
||||
|
||||
for(PolyphaseChannelSource channel : mChannels)
|
||||
{
|
||||
channel.processChannels(channels);
|
||||
channel.processChannels(complexChannelsBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,9 +33,12 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||
public class Channelizer implements ISourceEventProcessor, Listener<SourceEvent>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(Channelizer.class);
|
||||
private static final int CHANNEL_BANDWIDTH = 12500;
|
||||
private static final double CHANNEL_OVERSAMPLING = 2.0;
|
||||
|
||||
private Tuner mTuner;
|
||||
private List<PolyphaseChannelSource> mChannelSources = new CopyOnWriteArrayList<>();
|
||||
private ChannelizerConfiguration mChannelizerConfiguration;
|
||||
|
||||
/**
|
||||
* Provides access to Digital Drop Channel (DDC) sources from the tuner. Incorporates a polyphase channelizer
|
||||
@@ -46,12 +49,47 @@ public class Channelizer implements ISourceEventProcessor, Listener<SourceEvent>
|
||||
public Channelizer(Tuner tuner)
|
||||
{
|
||||
mTuner = tuner;
|
||||
|
||||
int sampleRate = mTuner.getTunerController().getSampleRate();
|
||||
|
||||
if(sampleRate % 25000 != 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Tuner sample rate [" + sampleRate + "] must be a multiple of 25 kHz");
|
||||
}
|
||||
|
||||
int channelCount = sampleRate / CHANNEL_BANDWIDTH;
|
||||
|
||||
long frequency = mTuner.getTunerController().getFrequency();
|
||||
|
||||
mChannelizerConfiguration = new ChannelizerConfiguration(frequency, channelCount,
|
||||
CHANNEL_BANDWIDTH, CHANNEL_OVERSAMPLING);
|
||||
|
||||
mTuner.addSourceEventListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
//TODO: return a polyphase channel here
|
||||
try
|
||||
{
|
||||
List<Integer> polyphaseIndexes = mChannelizerConfiguration.getPolyphaseChannelIndexes(tunerChannel);
|
||||
|
||||
|
||||
|
||||
}
|
||||
catch(IllegalArgumentException iae)
|
||||
{
|
||||
mLog.info("Can't provide DDC for " + tunerChannel.toString() + " due to channelizer frequency [" +
|
||||
" due to channelizer frequency [" + mChannelizerConfiguration.getCenterFrequency() +
|
||||
"] and sample rate [" + (mChannelizerConfiguration.getChannelCount() *
|
||||
mChannelizerConfiguration.getChannelBandwidth()) + "]");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -121,6 +159,16 @@ public class Channelizer implements ISourceEventProcessor, Listener<SourceEvent>
|
||||
(sourceEvent.hasSource() ? sourceEvent.getSource().getClass() : "null source"));
|
||||
}
|
||||
break;
|
||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||
mChannelizerConfiguration.setCenterFrequency(sourceEvent.getValue().longValue());
|
||||
updateChannelResultsProcessors();
|
||||
break;
|
||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||
int sampleRate = sourceEvent.getValue().intValue();
|
||||
int channelCount = sampleRate / mChannelizerConfiguration.getChannelBandwidth();
|
||||
mChannelizerConfiguration.setChannelCount(channelCount);
|
||||
updateChannelResultsProcessors();
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -130,6 +178,11 @@ public class Channelizer implements ISourceEventProcessor, Listener<SourceEvent>
|
||||
@Override
|
||||
public void receive(SourceEvent sourceEvent)
|
||||
{
|
||||
process(sourceEvent);
|
||||
}
|
||||
|
||||
private void updateChannelResultsProcessors()
|
||||
{
|
||||
//Update the processors anytime that the center frequency or sample rate changes
|
||||
}
|
||||
}
|
||||
|
||||
310
src/dsp/filter/channelizer/ChannelizerConfiguration.java
Normal file
310
src/dsp/filter/channelizer/ChannelizerConfiguration.java
Normal file
@@ -0,0 +1,310 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 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 dsp.filter.channelizer;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import source.tuner.TunerChannel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ChannelizerConfiguration
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelizerConfiguration.class);
|
||||
|
||||
private long mCenterFrequency;
|
||||
private int mChannelCount;
|
||||
private int mChannelBandwidth;
|
||||
private double mOversampling = 1.0;
|
||||
|
||||
/**
|
||||
* Configuration details for a polyphase channelizer that can be used to identify which polyphase
|
||||
* channelizer output channel(s) should be processed to produce an output channel of a specified
|
||||
* center frequency and channel sample rate/bandwidth.
|
||||
*
|
||||
* @param centerFrequency
|
||||
* @param channelCount
|
||||
* @param channelBandwidth
|
||||
* @param oversampling set to 1.0 for no oversample, or greater than 1.0 for oversampling
|
||||
*/
|
||||
public ChannelizerConfiguration(long centerFrequency, int channelCount, int channelBandwidth, double oversampling)
|
||||
{
|
||||
mCenterFrequency = centerFrequency;
|
||||
mChannelCount = channelCount;
|
||||
mChannelBandwidth = channelBandwidth;
|
||||
mOversampling = oversampling;
|
||||
}
|
||||
|
||||
/**
|
||||
* Center frequency for the channelizer
|
||||
*/
|
||||
public long getCenterFrequency()
|
||||
{
|
||||
return mCenterFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or changes the center frequency for the channelizer
|
||||
*/
|
||||
public void setCenterFrequency(long frequency)
|
||||
{
|
||||
mCenterFrequency = frequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of channels being channelized by the polyphase channelizer
|
||||
*/
|
||||
public int getChannelCount()
|
||||
{
|
||||
return mChannelCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of channels being channelized by the polyphase channelizer
|
||||
*/
|
||||
public void setChannelCount(int channelCount)
|
||||
{
|
||||
mChannelCount = channelCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel bandwidth. Note: this value may be smaller than the channel sample rate when oversampling
|
||||
* is being used.
|
||||
*/
|
||||
public int getChannelBandwidth()
|
||||
{
|
||||
return mChannelBandwidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Half of the channel bandwidth.
|
||||
*/
|
||||
public int getHalfChannelBandwidth()
|
||||
{
|
||||
return mChannelBandwidth / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the channel bandwidth in hertz.
|
||||
*/
|
||||
public void setChannelBandwidth(int channelBandwidth)
|
||||
{
|
||||
mChannelBandwidth = channelBandwidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Oversampling rate produced by the channelizer.
|
||||
*
|
||||
* @return 1.0 for no oversampling, or a value greater than 1.0 when oversampling is applied.
|
||||
*/
|
||||
public double getOversampling()
|
||||
{
|
||||
return mOversampling;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the oversampling rate being applied by the channelizer
|
||||
* @param oversampling rate where 1.0 indicates no oversampling, and a value greater than 1.0 indicates
|
||||
* that the polyphase channelizer is oversampling each channel.
|
||||
*/
|
||||
public void setOversampling(double oversampling)
|
||||
{
|
||||
mOversampling = oversampling;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel sample rate. This value is the product of the filtered channel bandwidth times the
|
||||
* oversampling rate.
|
||||
*/
|
||||
public double getChannelSampleRate()
|
||||
{
|
||||
return (double)getChannelBandwidth() * getOversampling();
|
||||
}
|
||||
|
||||
/**
|
||||
* Channelizer input sample rate
|
||||
* @return input sample rate in hertz
|
||||
*/
|
||||
public int getSampleRate()
|
||||
{
|
||||
return getChannelBandwidth() * getChannelCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Channelizer input minimum frequency
|
||||
* @return minimum frequency in hertz
|
||||
*/
|
||||
public long getMinimumFrequency()
|
||||
{
|
||||
return getCenterFrequency() - (getSampleRate() / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Channelizer input maximum frequency
|
||||
* @return maximum frequency in hertz
|
||||
*/
|
||||
public long getMaximumFrequency()
|
||||
{
|
||||
return getCenterFrequency() + (getSampleRate() / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies the channelizer output channel(s) required to produce a tuner channel with the specified center
|
||||
* frequency and channel bandwidth.
|
||||
*
|
||||
* @param tunerChannel specifying the desired channel frequency and bandwidth
|
||||
* @return an array of one or more polyphase channelizer channels to target in order to produce an output channel
|
||||
* for the desired frequency and bandwidth.
|
||||
* @throws IllegalArgumentException if the tuner channel's minimum or maximum frequency is not contained
|
||||
* within the channelizer's minimum or maximum frequency range
|
||||
*/
|
||||
public List<Integer> getPolyphaseChannelIndexes(TunerChannel tunerChannel) throws IllegalArgumentException
|
||||
{
|
||||
if(tunerChannel.getMinFrequency() < getMinimumFrequency() ||
|
||||
tunerChannel.getMaxFrequency() > getMaximumFrequency())
|
||||
{
|
||||
throw new IllegalArgumentException("Requested channel cannot be provided by this " +
|
||||
"channelizer. Requested channel [" + tunerChannel.getMinFrequency() + " - " +
|
||||
tunerChannel.getMaxFrequency() + "] exceeds current channelizer frequency range [" +
|
||||
getMinimumFrequency() + " - " + getMaximumFrequency() + "]");
|
||||
}
|
||||
|
||||
List<Integer> indices = new ArrayList<>();
|
||||
|
||||
long startFrequencyOffset = (tunerChannel.getMinFrequency() - getMinimumFrequency());
|
||||
int startIndex = (int)(startFrequencyOffset / getChannelBandwidth());
|
||||
|
||||
long endFrequencyOffset = (tunerChannel.getMaxFrequency() - getMinimumFrequency());
|
||||
int endIndex = (int)(endFrequencyOffset / getChannelBandwidth());
|
||||
|
||||
int currentIndex = startIndex;
|
||||
|
||||
while(currentIndex <= endIndex)
|
||||
{
|
||||
long currentIndexMin = getMinimumFrequencyForChannelIndex(currentIndex);
|
||||
long currentIndexMax = getMaximumFrequencyForChannelIndex(currentIndex);
|
||||
|
||||
//Case 1: tuner channel completely aligns with indexed channel
|
||||
if(currentIndexMin <= tunerChannel.getMinFrequency() && tunerChannel.getMaxFrequency() <= currentIndexMax)
|
||||
{
|
||||
indices.add(currentIndex);
|
||||
}
|
||||
//Case 2: tuner channel min frequency aligns and max frequency exceeds
|
||||
else if(currentIndexMin <= tunerChannel.getMinFrequency() &&
|
||||
tunerChannel.getMinFrequency() < currentIndexMax &&
|
||||
tunerChannel.getMaxFrequency() > currentIndexMax)
|
||||
{
|
||||
indices.add(currentIndex);
|
||||
}
|
||||
//Case 3: tuner channel min frequency exceeds and max frequency aligns
|
||||
else if(tunerChannel.getMinFrequency() < currentIndexMin &&
|
||||
tunerChannel.getMaxFrequency() > currentIndexMin &&
|
||||
tunerChannel.getMaxFrequency() <= currentIndexMax)
|
||||
{
|
||||
indices.add(currentIndex);
|
||||
}
|
||||
//Case 4: tuner channel encapsulates the indexed channel
|
||||
else if(tunerChannel.getMinFrequency() < currentIndexMin && tunerChannel.getMaxFrequency() > currentIndexMax)
|
||||
{
|
||||
indices.add(currentIndex);
|
||||
}
|
||||
|
||||
currentIndex++;
|
||||
}
|
||||
|
||||
return indices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the center frequency for the channel identified by the index in the channel results array.
|
||||
*
|
||||
* @param index to determine the channel center frequency
|
||||
* @return center frequency for the indexed channel
|
||||
*/
|
||||
public long getCenterFrequencyForChannelIndex(int index)
|
||||
{
|
||||
if(index < 0 || index >= getChannelCount())
|
||||
{
|
||||
throw new IllegalArgumentException("Illegal channel index");
|
||||
}
|
||||
|
||||
return getMinimumFrequency() + getHalfChannelBandwidth() + (index * getChannelBandwidth());
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the minimum frequency band edge for the channel identified by the index in the channel results
|
||||
* array.
|
||||
* @param index to calculate the minimum band edge frequency for the channel.
|
||||
* @return frequency of the minimum band edge
|
||||
*/
|
||||
public long getMinimumFrequencyForChannelIndex(int index)
|
||||
{
|
||||
if(index < 0 || index > getChannelCount())
|
||||
{
|
||||
throw new IllegalArgumentException("Can't determine minimum frequency - illegal channel index [" +
|
||||
index + "]");
|
||||
}
|
||||
|
||||
return getMinimumFrequency() + (index * getChannelBandwidth());
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the maximum frequency band edge for the channel identified by the index in the channel results
|
||||
* array.
|
||||
* @param index to calculate the maximum band edge frequency for the channel.
|
||||
* @return frequency of the maximum band edge
|
||||
*/
|
||||
public long getMaximumFrequencyForChannelIndex(int index)
|
||||
{
|
||||
if(index < 0 || index > getChannelCount())
|
||||
{
|
||||
throw new IllegalArgumentException("Can't determine maximum frequency - illegal channel index [" +
|
||||
index + "]");
|
||||
}
|
||||
|
||||
//Maximum frequency is the same as the minimum frequency of the next higher indexed channel
|
||||
return getMinimumFrequency() + ((index + 1) * getChannelBandwidth());
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
mLog.debug("Starting ....");
|
||||
|
||||
int channelCount = 10;
|
||||
|
||||
ChannelizerConfiguration channelizerConfiguration =
|
||||
new ChannelizerConfiguration(100062500, channelCount, 12500, 2.0);
|
||||
|
||||
for(int x = 0; x < channelCount; x++)
|
||||
{
|
||||
mLog.debug("Channel " + x + ": " + channelizerConfiguration.getMinimumFrequencyForChannelIndex(x) + " - " +
|
||||
channelizerConfiguration.getMaximumFrequencyForChannelIndex(x) +
|
||||
" Center: " + channelizerConfiguration.getCenterFrequencyForChannelIndex(x));
|
||||
}
|
||||
|
||||
TunerChannel tunerChannel = new TunerChannel(100006250, 12500);
|
||||
|
||||
List<Integer> indices = channelizerConfiguration.getPolyphaseChannelIndexes(tunerChannel);
|
||||
|
||||
mLog.debug("Indices: " + indices);
|
||||
|
||||
mLog.debug("Finished!");
|
||||
}
|
||||
}
|
||||
@@ -224,7 +224,7 @@ public class ChannelizerViewer extends JFrame
|
||||
|
||||
mPolyphaseChannelizer = new ComplexPolyphaseChannelizer(taps, mChannelCount, CHANNEL_BANDWIDTH);
|
||||
mChannelDistributor = new ChannelDistributor(bufferSize, mChannelCount);
|
||||
mPolyphaseChannelizer.setChannelDistributor(mChannelDistributor);
|
||||
// mPolyphaseChannelizer.setChannelDistributor(mChannelDistributor);
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
@@ -20,16 +20,15 @@ package dsp.filter.channelizer;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sample.Listener;
|
||||
import sample.OverflowableTransferQueue;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import sample.real.IOverflowListener;
|
||||
import source.ISourceEventProcessor;
|
||||
import source.SourceEvent;
|
||||
import source.SourceException;
|
||||
import source.tuner.ComplexChannelSource;
|
||||
import source.tuner.TunerChannel;
|
||||
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
public class PolyphaseChannelSource extends ComplexChannelSource
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class);
|
||||
@@ -40,6 +39,7 @@ public class PolyphaseChannelSource extends ComplexChannelSource
|
||||
private int mChannelSampleRate;
|
||||
private int mChannelCount;
|
||||
private int mOutputFrequencyCorrection;
|
||||
private ChannelBufferProcessor mProcessor = new ChannelBufferProcessor();
|
||||
|
||||
/**
|
||||
* Polyphase channelizer channel implementation. Adapts the channel array output samples from the polyphase
|
||||
@@ -86,13 +86,11 @@ public class PolyphaseChannelSource extends ComplexChannelSource
|
||||
|
||||
/**
|
||||
* Process the array samples for each of the output channels from the polyphase channelizer.
|
||||
* @param channels
|
||||
* @param channelsBuffer
|
||||
*/
|
||||
public void processChannels(float[] channels)
|
||||
public void processChannels(ComplexBuffer channelsBuffer)
|
||||
{
|
||||
|
||||
//TODO we have to dispatch full buffers on a separate thread so that we don't bog down the channelizer
|
||||
//TODO thread that is delivering the channel samples to us.
|
||||
mProcessor.process(channelsBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,4 +196,46 @@ public class PolyphaseChannelSource extends ComplexChannelSource
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class ChannelBufferProcessor implements Runnable
|
||||
{
|
||||
private OverflowableTransferQueue<ComplexBuffer> mComplexChannelBuffers;
|
||||
|
||||
public ChannelBufferProcessor()
|
||||
{
|
||||
mComplexChannelBuffers = new OverflowableTransferQueue<>(500, 10);
|
||||
mComplexChannelBuffers.setOverflowListener(new IOverflowListener()
|
||||
{
|
||||
@Override
|
||||
public void sourceOverflow(boolean overflow)
|
||||
{
|
||||
broadcastOverflowState(overflow);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the polyphase analysis filter's channel output for post channelizer mixing, filtering
|
||||
* and resampling operations and buffers output for dispatch to downstream processing.
|
||||
*
|
||||
* @param channelsBuffer to enqueue for processing
|
||||
*/
|
||||
public void process(ComplexBuffer channelsBuffer)
|
||||
{
|
||||
mComplexChannelBuffers.offer(channelsBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
}
|
||||
catch(Throwable throwable)
|
||||
{
|
||||
mLog.error("Error while processing polyphase channel samples", throwable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 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 dsp.filter.channelizer.output;
|
||||
|
||||
public class ChannelOutputProcessorFactory
|
||||
{
|
||||
/**
|
||||
* Factory for obtaining polyphase channelizer results processors to perform any post-processing
|
||||
* that may be required to perform frequency translation and/or resampling of channel results.
|
||||
*/
|
||||
public static IPolyphaseChannelOutputProcessor getProcessor(int sampleRate, long frequency)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 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 dsp.filter.channelizer.output;
|
||||
|
||||
import sample.complex.IComplexSampleListener;
|
||||
|
||||
public interface IPolyphaseChannelOutputProcessor
|
||||
{
|
||||
/**
|
||||
* Process the channel output channel results from the polyphase channelizer filter
|
||||
*
|
||||
* @param channelResults to process
|
||||
* @param listener to receive the processed channel results
|
||||
*/
|
||||
void process(float[] channelResults, IComplexSampleListener listener);
|
||||
|
||||
/**
|
||||
* Specifies the positive or negative frequency correction that should be applied to samples extracted from
|
||||
* the polyphase channelizer output results.
|
||||
* @param frequency
|
||||
*/
|
||||
void setFrequencyCorrection(long frequency);
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 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 dsp.filter.channelizer.output;
|
||||
|
||||
import dsp.mixer.Oscillator;
|
||||
import sample.complex.Complex;
|
||||
import sample.complex.IComplexSampleListener;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SingleChannelOutputProcessor implements IPolyphaseChannelOutputProcessor
|
||||
{
|
||||
private int mChannelOffset;
|
||||
private Oscillator mFrequencyCorrectionMixer;
|
||||
|
||||
/**
|
||||
* Processor to extract a single channel from a polyphase channelizer and produce an output I/Q complex sample
|
||||
* from each polyphase channelizer output results array.
|
||||
*
|
||||
* @param sampleRate of the output sample stream.
|
||||
* @param channelIndexes containing a single channel index.
|
||||
*/
|
||||
public SingleChannelOutputProcessor(int sampleRate, List<Integer> channelIndexes)
|
||||
{
|
||||
mFrequencyCorrectionMixer = new Oscillator(0, sampleRate);
|
||||
setPolyphaseChannelIndices(channelIndexes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates this processor to extract a specified channel index.
|
||||
* @param indexes containing a single channel index value.
|
||||
* @throws IllegalArgumentException if the list of indexes does not contain a single channel index.
|
||||
*/
|
||||
public void setPolyphaseChannelIndices(List<Integer> indexes)
|
||||
{
|
||||
if(indexes.size() != 1)
|
||||
{
|
||||
throw new IllegalArgumentException("Single channel output processor requires a single index to " +
|
||||
"process - provided indexes " + indexes.toString());
|
||||
}
|
||||
|
||||
//Set the channelized output results offset to twice the channel index to account for each channel having
|
||||
//an I/Q pair
|
||||
mChannelOffset = indexes.get(0) * 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the channel from the channel results array, applies frequency translation, and delivers the
|
||||
* extracted frequency-translated channel sample to the complex sample listener.
|
||||
*
|
||||
* @param channelResults to process containing an array of channel I/Q sample pairs (I0,Q0,I1,Q1...In,Qn)
|
||||
* @param listener to receive the extracted, frequency-translated channel results
|
||||
*/
|
||||
@Override
|
||||
public void process(float[] channelResults, IComplexSampleListener listener)
|
||||
{
|
||||
if(channelResults.length >= (mChannelOffset + 1))
|
||||
{
|
||||
//Only perform frequency translation if the frequency correction mixer has a non-zero frequency value
|
||||
if(mFrequencyCorrectionMixer.isEnabled())
|
||||
{
|
||||
mFrequencyCorrectionMixer.rotate();
|
||||
|
||||
float i = Complex.multiplyInphase(channelResults[mChannelOffset], channelResults[mChannelOffset + 1],
|
||||
mFrequencyCorrectionMixer.inphase(), mFrequencyCorrectionMixer.quadrature());
|
||||
|
||||
float q = Complex.multiplyQuadrature(channelResults[mChannelOffset], channelResults[mChannelOffset + 1],
|
||||
mFrequencyCorrectionMixer.inphase(), mFrequencyCorrectionMixer.quadrature());
|
||||
|
||||
listener.receive(i, q);
|
||||
}
|
||||
else
|
||||
{
|
||||
listener.receive(channelResults[mChannelOffset], channelResults[mChannelOffset + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies a frequency correction/translation value to be applied to channel samples output from the
|
||||
* polyphase channelizer.
|
||||
* @param frequency
|
||||
*/
|
||||
@Override
|
||||
public void setFrequencyCorrection(long frequency)
|
||||
{
|
||||
mFrequencyCorrectionMixer.setFrequency(frequency);
|
||||
}
|
||||
}
|
||||
@@ -1,135 +1,152 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 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/>
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 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 dsp.mixer;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.complex.Complex;
|
||||
import sample.complex.ComplexBuffer;
|
||||
|
||||
public class Oscillator
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( Oscillator.class );
|
||||
private final static Logger mLog = LoggerFactory.getLogger(Oscillator.class);
|
||||
|
||||
private double mFrequency;
|
||||
private double mSampleRate;
|
||||
private boolean mEnabled = true;
|
||||
private double mFrequency;
|
||||
private double mSampleRate;
|
||||
|
||||
private Complex mAnglePerSample;
|
||||
private Complex mCurrentAngle = new Complex( 0.0f, -1.0f );
|
||||
private Complex mAnglePerSample;
|
||||
private Complex mCurrentAngle = new Complex(0.0f, -1.0f);
|
||||
|
||||
/**
|
||||
* Oscillator produces complex or float samples corresponding to a sine wave
|
||||
* oscillating at the specified frequency and sample rate
|
||||
*
|
||||
* @param frequency - positive or negative frequency in hertz
|
||||
* @param sampleRate - in hertz
|
||||
*/
|
||||
public Oscillator( long frequency, int sampleRate )
|
||||
{
|
||||
mSampleRate = (double)sampleRate;
|
||||
mFrequency = (double)frequency;
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the internal values after a frequency or sample rate change
|
||||
*/
|
||||
private void update()
|
||||
{
|
||||
float anglePerSample =
|
||||
(float)( 2.0d * Math.PI * mFrequency / mSampleRate );
|
||||
/**
|
||||
* Oscillator produces complex or float samples corresponding to a sine wave
|
||||
* oscillating at the specified frequency and sample rate
|
||||
*
|
||||
* @param frequency - positive or negative frequency in hertz
|
||||
* @param sampleRate - in hertz
|
||||
*/
|
||||
public Oscillator(long frequency, int sampleRate)
|
||||
{
|
||||
mEnabled = frequency != 0;
|
||||
|
||||
mAnglePerSample = Complex.fromAngle( anglePerSample );
|
||||
}
|
||||
mSampleRate = (double)sampleRate;
|
||||
mFrequency = (double)frequency;
|
||||
|
||||
/**
|
||||
* Sets or changes the frequency of this oscillator
|
||||
*/
|
||||
public void setFrequency( long frequency )
|
||||
{
|
||||
mFrequency = (double)frequency;
|
||||
update();
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or changes the sample rate of this oscillator
|
||||
*/
|
||||
public void setSampleRate( int sampleRate )
|
||||
{
|
||||
mSampleRate = (double)sampleRate;
|
||||
update();
|
||||
}
|
||||
/**
|
||||
* Indicates if this oscillator has a non-zero frequency setting. When used as a frequency translating
|
||||
* mixer, setting a frequency of zero indicates that no frequency correction is needed, so this flag can
|
||||
* indicate when to apply the oscillator to frequency correct samples.
|
||||
*
|
||||
* @return true if the current frequency value is non-zero.
|
||||
*/
|
||||
public boolean isEnabled()
|
||||
{
|
||||
return mEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Steps the current angle by the angle per sample amount
|
||||
*/
|
||||
public void rotate()
|
||||
{
|
||||
mCurrentAngle.multiply( mAnglePerSample );
|
||||
mCurrentAngle.fastNormalize();
|
||||
}
|
||||
/**
|
||||
* Updates the internal values after a frequency or sample rate change
|
||||
*/
|
||||
private void update()
|
||||
{
|
||||
float anglePerSample =
|
||||
(float)(2.0d * Math.PI * mFrequency / mSampleRate);
|
||||
|
||||
public float inphase()
|
||||
{
|
||||
return mCurrentAngle.inphase();
|
||||
}
|
||||
|
||||
public float quadrature()
|
||||
{
|
||||
return mCurrentAngle.quadrature();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get next complex sample
|
||||
*/
|
||||
public Complex getComplex()
|
||||
{
|
||||
return mCurrentAngle.copy();
|
||||
}
|
||||
mAnglePerSample = Complex.fromAngle(anglePerSample);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next float sample
|
||||
*/
|
||||
public float getFloat()
|
||||
{
|
||||
return mCurrentAngle.real();
|
||||
}
|
||||
/**
|
||||
* Sets or changes the frequency of this oscillator
|
||||
*/
|
||||
public void setFrequency(long frequency)
|
||||
{
|
||||
mEnabled = frequency != 0;
|
||||
mFrequency = (double)frequency;
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the requested number of complex samples and returns them in a complex buffer
|
||||
* @param sampleCount to generate
|
||||
* @return complex buffer containing the requested complex samples
|
||||
*/
|
||||
public ComplexBuffer generateComplexBuffer(int sampleCount)
|
||||
{
|
||||
float[] buffer = new float[sampleCount * 2];
|
||||
/**
|
||||
* Sets or changes the sample rate of this oscillator
|
||||
*/
|
||||
public void setSampleRate(int sampleRate)
|
||||
{
|
||||
mSampleRate = (double)sampleRate;
|
||||
update();
|
||||
}
|
||||
|
||||
for(int x = 0; x < sampleCount; x++)
|
||||
{
|
||||
int index = 2 * x;
|
||||
buffer[index] = inphase();
|
||||
buffer[index + 1] = quadrature();
|
||||
/**
|
||||
* Steps the current angle by the angle per sample amount
|
||||
*/
|
||||
public void rotate()
|
||||
{
|
||||
mCurrentAngle.multiply(mAnglePerSample);
|
||||
mCurrentAngle.fastNormalize();
|
||||
}
|
||||
|
||||
rotate();
|
||||
}
|
||||
public float inphase()
|
||||
{
|
||||
return mCurrentAngle.inphase();
|
||||
}
|
||||
|
||||
return new ComplexBuffer(buffer);
|
||||
}
|
||||
public float quadrature()
|
||||
{
|
||||
return mCurrentAngle.quadrature();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get next complex sample
|
||||
*/
|
||||
public Complex getComplex()
|
||||
{
|
||||
return mCurrentAngle.copy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next float sample
|
||||
*/
|
||||
public float getFloat()
|
||||
{
|
||||
return mCurrentAngle.real();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the requested number of complex samples and returns them in a complex buffer
|
||||
*
|
||||
* @param sampleCount to generate
|
||||
* @return complex buffer containing the requested complex samples
|
||||
*/
|
||||
public ComplexBuffer generateComplexBuffer(int sampleCount)
|
||||
{
|
||||
float[] buffer = new float[sampleCount * 2];
|
||||
|
||||
for(int x = 0; x < sampleCount; x++)
|
||||
{
|
||||
int index = 2 * x;
|
||||
buffer[index] = inphase();
|
||||
buffer[index + 1] = quadrature();
|
||||
|
||||
rotate();
|
||||
}
|
||||
|
||||
return new ComplexBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 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/>
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 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 source;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user