mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-13 00:57:31 -05:00
Merge pull request #430 from DSheirer/429-polyphase-channelizer-25khz-channel-rate
429 polyphase channelizer 25khz channel rate
This commit is contained in:
@@ -63,7 +63,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
|||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(ComplexPolyphaseChannelizerM2.class);
|
private final static Logger mLog = LoggerFactory.getLogger(ComplexPolyphaseChannelizerM2.class);
|
||||||
|
|
||||||
private static final int DEFAULT_MINIMUM_CHANNEL_BANDWIDTH = 12500;
|
private static final int DEFAULT_MINIMUM_CHANNEL_BANDWIDTH = 25000;
|
||||||
|
|
||||||
//Sized at 152 buffers a second where max = 5 seconds and reset = 2 seconds worth of buffers
|
//Sized at 152 buffers a second where max = 5 seconds and reset = 2 seconds worth of buffers
|
||||||
private IFFTProcessor mIFFTProcessor = new IFFTProcessor((5 * 152), (2 * 152));
|
private IFFTProcessor mIFFTProcessor = new IFFTProcessor((5 * 152), (2 * 152));
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import io.github.dsheirer.source.Source;
|
|||||||
import io.github.dsheirer.source.SourceEvent;
|
import io.github.dsheirer.source.SourceEvent;
|
||||||
import io.github.dsheirer.source.SourceException;
|
import io.github.dsheirer.source.SourceException;
|
||||||
import io.github.dsheirer.source.tuner.TunerController;
|
import io.github.dsheirer.source.tuner.TunerController;
|
||||||
|
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
||||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -62,7 +63,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
|||||||
public class PolyphaseChannelManager implements ISourceEventProcessor
|
public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
|
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
|
||||||
private static final double MINIMUM_CHANNEL_BANDWIDTH = 12500.0;
|
private static final double MINIMUM_CHANNEL_BANDWIDTH = 25000.0;
|
||||||
private static final double CHANNEL_OVERSAMPLING = 2.0;
|
private static final double CHANNEL_OVERSAMPLING = 2.0;
|
||||||
private static final int POLYPHASE_CHANNELIZER_TAPS_PER_CHANNEL = 9;
|
private static final int POLYPHASE_CHANNELIZER_TAPS_PER_CHANNEL = 9;
|
||||||
private static final int POLYPHASE_SYNTHESIZER_TAPS_PER_CHANNEL = 9;
|
private static final int POLYPHASE_SYNTHESIZER_TAPS_PER_CHANNEL = 9;
|
||||||
@@ -141,7 +142,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
|||||||
* @param tunerChannel specifying center frequency and bandwidth.
|
* @param tunerChannel specifying center frequency and bandwidth.
|
||||||
* @return source or null.
|
* @return source or null.
|
||||||
*/
|
*/
|
||||||
public TunerChannelSource getChannel(TunerChannel tunerChannel)
|
public TunerChannelSource getChannel(TunerChannel tunerChannel, ChannelSpecification channelSpecification)
|
||||||
{
|
{
|
||||||
PolyphaseChannelSource channelSource = null;
|
PolyphaseChannelSource channelSource = null;
|
||||||
|
|
||||||
@@ -155,10 +156,17 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
|||||||
{
|
{
|
||||||
long centerFrequency = mChannelCalculator.getCenterFrequencyForIndexes(polyphaseIndexes);
|
long centerFrequency = mChannelCalculator.getCenterFrequencyForIndexes(polyphaseIndexes);
|
||||||
|
|
||||||
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
|
try
|
||||||
mChannelCalculator.getChannelSampleRate(), centerFrequency);
|
{
|
||||||
|
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
|
||||||
|
mChannelCalculator.getChannelSampleRate(), centerFrequency, channelSpecification);
|
||||||
|
|
||||||
mChannelSources.add(channelSource);
|
mChannelSources.add(channelSource);
|
||||||
|
}
|
||||||
|
catch(FilterDesignException fde)
|
||||||
|
{
|
||||||
|
mLog.debug("Couldn't design final output low pass filter for polyphase channel source");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(IllegalArgumentException iae)
|
catch(IllegalArgumentException iae)
|
||||||
|
|||||||
@@ -16,22 +16,35 @@
|
|||||||
package io.github.dsheirer.dsp.filter.channelizer;
|
package io.github.dsheirer.dsp.filter.channelizer;
|
||||||
|
|
||||||
import io.github.dsheirer.dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor;
|
import io.github.dsheirer.dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor;
|
||||||
|
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||||
|
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||||
|
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2;
|
||||||
|
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
|
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
|
||||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||||
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
|
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
|
||||||
import io.github.dsheirer.source.SourceEvent;
|
import io.github.dsheirer.source.SourceEvent;
|
||||||
|
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
||||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class PolyphaseChannelSource extends TunerChannelSource
|
public class PolyphaseChannelSource extends TunerChannelSource
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class);
|
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class);
|
||||||
|
|
||||||
private ReusableComplexBufferAssembler mReusableComplexBufferAssembler;
|
private ReusableComplexBufferAssembler mReusableComplexBufferAssembler;
|
||||||
private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor;
|
private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor;
|
||||||
|
private IPolyphaseChannelOutputProcessor mReplacementPolyphaseChannelOutputProcessor;
|
||||||
|
private long mReplacementFrequency;
|
||||||
|
private ComplexFIRFilter2 mLowPassFilter;
|
||||||
|
private static Map<Integer,float[]> sLowPassFilters = new HashMap();
|
||||||
|
|
||||||
private double mChannelSampleRate;
|
private double mChannelSampleRate;
|
||||||
private long mIndexCenterFrequency;
|
private long mIndexCenterFrequency;
|
||||||
private long mChannelFrequencyCorrection;
|
private long mChannelFrequencyCorrection;
|
||||||
@@ -46,15 +59,22 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
|||||||
* @param producerSourceEventListener to receive source event requests (e.g. start/stop sample stream)
|
* @param producerSourceEventListener to receive source event requests (e.g. start/stop sample stream)
|
||||||
* @param channelSampleRate for the downstream sample output
|
* @param channelSampleRate for the downstream sample output
|
||||||
* @param centerFrequency of the incoming polyphase channel(s)
|
* @param centerFrequency of the incoming polyphase channel(s)
|
||||||
|
* @throws FilterDesignException if a channel low pass filter can't be designed to the channel specification
|
||||||
*/
|
*/
|
||||||
public PolyphaseChannelSource(TunerChannel tunerChannel, IPolyphaseChannelOutputProcessor outputProcessor,
|
public PolyphaseChannelSource(TunerChannel tunerChannel, IPolyphaseChannelOutputProcessor outputProcessor,
|
||||||
Listener<SourceEvent> producerSourceEventListener, double channelSampleRate,
|
Listener<SourceEvent> producerSourceEventListener, double channelSampleRate,
|
||||||
long centerFrequency)
|
long centerFrequency, ChannelSpecification channelSpecification)
|
||||||
|
throws FilterDesignException
|
||||||
{
|
{
|
||||||
super(producerSourceEventListener, tunerChannel);
|
super(producerSourceEventListener, tunerChannel);
|
||||||
mPolyphaseChannelOutputProcessor = outputProcessor;
|
mPolyphaseChannelOutputProcessor = outputProcessor;
|
||||||
mChannelSampleRate = channelSampleRate;
|
mChannelSampleRate = channelSampleRate;
|
||||||
mReusableComplexBufferAssembler = new ReusableComplexBufferAssembler(2500, mChannelSampleRate);
|
mReusableComplexBufferAssembler = new ReusableComplexBufferAssembler(2500, mChannelSampleRate);
|
||||||
|
|
||||||
|
float[] filterCoefficients = getLowPassFilter(channelSampleRate, channelSpecification.getPassFrequency(),
|
||||||
|
channelSpecification.getStopFrequency());
|
||||||
|
mLowPassFilter = new ComplexFIRFilter2(filterCoefficients, 1.0f);
|
||||||
|
|
||||||
setFrequency(centerFrequency);
|
setFrequency(centerFrequency);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,14 +113,33 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
|||||||
*/
|
*/
|
||||||
public void setPolyphaseChannelOutputProcessor(IPolyphaseChannelOutputProcessor outputProcessor, long frequency)
|
public void setPolyphaseChannelOutputProcessor(IPolyphaseChannelOutputProcessor outputProcessor, long frequency)
|
||||||
{
|
{
|
||||||
//Lock on the channel output processor to block the channel results processor thread from servicing the
|
//If this is the first time, simply assign the output processor
|
||||||
//channel output processor's buffer while we change out the processors.
|
if(mPolyphaseChannelOutputProcessor == null)
|
||||||
synchronized(mPolyphaseChannelOutputProcessor)
|
{
|
||||||
|
mPolyphaseChannelOutputProcessor = outputProcessor;
|
||||||
|
}
|
||||||
|
//Otherwise, we have to swap out the processor on the sample processing thread
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mReplacementPolyphaseChannelOutputProcessor = outputProcessor;
|
||||||
|
mReplacementFrequency = frequency;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the output processor to use the new output processor provided by the external
|
||||||
|
* polyphase channel manager. This method should only be executed on the sample processing
|
||||||
|
* thread, within the processChannelResults() method.
|
||||||
|
*/
|
||||||
|
private void swapOutputProcessor()
|
||||||
|
{
|
||||||
|
if(mReplacementPolyphaseChannelOutputProcessor != null)
|
||||||
{
|
{
|
||||||
IPolyphaseChannelOutputProcessor existingProcessor = mPolyphaseChannelOutputProcessor;
|
IPolyphaseChannelOutputProcessor existingProcessor = mPolyphaseChannelOutputProcessor;
|
||||||
|
|
||||||
//Swap out the processor so that incoming samples can accumulate in the new channel output processor
|
//Swap out the processor so that incoming samples can accumulate in the new channel output processor
|
||||||
mPolyphaseChannelOutputProcessor = outputProcessor;
|
mPolyphaseChannelOutputProcessor = mReplacementPolyphaseChannelOutputProcessor;
|
||||||
|
mReplacementPolyphaseChannelOutputProcessor = null;
|
||||||
|
|
||||||
//Remove the buffer overflow listener and clear any previous buffer overflow state
|
//Remove the buffer overflow listener and clear any previous buffer overflow state
|
||||||
existingProcessor.setSourceOverflowListener(null);
|
existingProcessor.setSourceOverflowListener(null);
|
||||||
@@ -109,18 +148,9 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
|||||||
//Register to receive buffer overflow notifications so we can push them up through the source chain
|
//Register to receive buffer overflow notifications so we can push them up through the source chain
|
||||||
mPolyphaseChannelOutputProcessor.setSourceOverflowListener(this);
|
mPolyphaseChannelOutputProcessor.setSourceOverflowListener(this);
|
||||||
|
|
||||||
//Fully process the residual channel results buffer of the previous channel output processor
|
//Finally, setup the frequency offset for the output processor.
|
||||||
if(existingProcessor != null)
|
mIndexCenterFrequency = mReplacementFrequency;
|
||||||
{
|
|
||||||
existingProcessor.processChannelResults(mReusableComplexBufferAssembler);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Finally, setup the frequency offset for the output processor and reset the frequency correction value
|
|
||||||
//to allow consumers to adjust and calculate a new frequency correction value, if needed.
|
|
||||||
mIndexCenterFrequency = frequency;
|
|
||||||
mChannelFrequencyCorrection = 0;
|
|
||||||
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
|
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
|
||||||
broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,12 +163,13 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
|||||||
*/
|
*/
|
||||||
public void receiveChannelResults(ReusableChannelResultsBuffer channelResultsBuffer)
|
public void receiveChannelResults(ReusableChannelResultsBuffer channelResultsBuffer)
|
||||||
{
|
{
|
||||||
mReusableComplexBufferAssembler.updateTimestamp(channelResultsBuffer.getTimestamp());
|
if(mReplacementPolyphaseChannelOutputProcessor != null)
|
||||||
|
|
||||||
if(mPolyphaseChannelOutputProcessor != null)
|
|
||||||
{
|
{
|
||||||
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsBuffer);
|
swapOutputProcessor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mReusableComplexBufferAssembler.updateTimestamp(channelResultsBuffer.getTimestamp());
|
||||||
|
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -173,7 +204,6 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
|||||||
if(mPolyphaseChannelOutputProcessor != null)
|
if(mPolyphaseChannelOutputProcessor != null)
|
||||||
{
|
{
|
||||||
mPolyphaseChannelOutputProcessor.dispose();
|
mPolyphaseChannelOutputProcessor.dispose();
|
||||||
mPolyphaseChannelOutputProcessor = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,11 +243,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
|||||||
@Override
|
@Override
|
||||||
protected void processSamples()
|
protected void processSamples()
|
||||||
{
|
{
|
||||||
//Lock on the output processor so that it can't be changed in the middle of processing
|
mPolyphaseChannelOutputProcessor.processChannelResults(mReusableComplexBufferAssembler);
|
||||||
synchronized(mPolyphaseChannelOutputProcessor)
|
|
||||||
{
|
|
||||||
mPolyphaseChannelOutputProcessor.processChannelResults(mReusableComplexBufferAssembler);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -236,10 +262,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
|||||||
*/
|
*/
|
||||||
private void updateFrequencyOffset()
|
private void updateFrequencyOffset()
|
||||||
{
|
{
|
||||||
synchronized(mPolyphaseChannelOutputProcessor)
|
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
|
||||||
{
|
|
||||||
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -248,4 +271,42 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
|||||||
return "POLYPHASE [" + mPolyphaseChannelOutputProcessor.getInputChannelCount() + "] " +
|
return "POLYPHASE [" + mPolyphaseChannelOutputProcessor.getInputChannelCount() + "] " +
|
||||||
getTunerChannel().getFrequency();
|
getTunerChannel().getFrequency();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a low-pass filter to use as the final cleanup filter for the decimated output stream
|
||||||
|
*
|
||||||
|
* @param sampleRate for the final output channel rate
|
||||||
|
* @param passFrequency for half of the desired channel rate
|
||||||
|
* @param stopFrequency for the attenuated band
|
||||||
|
* @return a newly designed filter or a previously designed (cached) filter
|
||||||
|
* @throws FilterDesignException
|
||||||
|
*/
|
||||||
|
private float[] getLowPassFilter(double sampleRate, double passFrequency, double stopFrequency) throws FilterDesignException
|
||||||
|
{
|
||||||
|
//Use existing filter if we've already designed one
|
||||||
|
if(sLowPassFilters.containsKey((int)sampleRate))
|
||||||
|
{
|
||||||
|
return sLowPassFilters.get((int)sampleRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||||
|
.sampleRate(sampleRate)
|
||||||
|
.gridDensity(16)
|
||||||
|
.passBandCutoff(passFrequency)
|
||||||
|
.passBandAmplitude(1.0)
|
||||||
|
.passBandRipple(0.01)
|
||||||
|
.stopBandStart(stopFrequency)
|
||||||
|
.stopBandAmplitude(0.0)
|
||||||
|
.stopBandRipple(0.01)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
|
||||||
|
|
||||||
|
//This will throw an exception if the filter cannot be designed
|
||||||
|
float[] taps = designer.getImpulseResponse();
|
||||||
|
|
||||||
|
sLowPassFilters.put((int)sampleRate, taps);
|
||||||
|
|
||||||
|
return taps;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,29 +16,18 @@
|
|||||||
package io.github.dsheirer.dsp.filter.channelizer.output;
|
package io.github.dsheirer.dsp.filter.channelizer.output;
|
||||||
|
|
||||||
import io.github.dsheirer.dsp.filter.channelizer.TwoChannelSynthesizerM2;
|
import io.github.dsheirer.dsp.filter.channelizer.TwoChannelSynthesizerM2;
|
||||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
|
||||||
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
|
||||||
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2;
|
|
||||||
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
|
||||||
import io.github.dsheirer.dsp.mixer.FS4DownConverter;
|
import io.github.dsheirer.dsp.mixer.FS4DownConverter;
|
||||||
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
|
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
|
||||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||||
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
|
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class TwoChannelOutputProcessor extends ChannelOutputProcessor
|
public class TwoChannelOutputProcessor extends ChannelOutputProcessor
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(TwoChannelOutputProcessor.class);
|
// private final static Logger mLog = LoggerFactory.getLogger(TwoChannelOutputProcessor.class);
|
||||||
|
|
||||||
private static Map<Integer,float[]> sLowPassFilterKernelMap = new HashMap<>();
|
|
||||||
private TwoChannelSynthesizerM2 mSynthesizer;
|
private TwoChannelSynthesizerM2 mSynthesizer;
|
||||||
private FS4DownConverter mFS4DownConverter = new FS4DownConverter();
|
private FS4DownConverter mFS4DownConverter = new FS4DownConverter();
|
||||||
private ComplexFIRFilter2 mLowPassFilter;
|
|
||||||
|
|
||||||
private int mChannelOffset1;
|
private int mChannelOffset1;
|
||||||
private int mChannelOffset2;
|
private int mChannelOffset2;
|
||||||
@@ -58,28 +47,11 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
|
|||||||
super(2, sampleRate, gain);
|
super(2, sampleRate, gain);
|
||||||
setPolyphaseChannelIndices(channelIndexes);
|
setPolyphaseChannelIndices(channelIndexes);
|
||||||
setSynthesisFilter(filter);
|
setSynthesisFilter(filter);
|
||||||
|
|
||||||
float[] lowPassFilter = sLowPassFilterKernelMap.get((int)sampleRate);
|
|
||||||
|
|
||||||
if(lowPassFilter == null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
lowPassFilter = getLowPassFilter(sampleRate);
|
|
||||||
sLowPassFilterKernelMap.put((int)sampleRate, lowPassFilter);
|
|
||||||
}
|
|
||||||
catch(FilterDesignException fde)
|
|
||||||
{
|
|
||||||
mLog.error("Couldn't design a low-pass filter for 2-channel synthesizer", fde);
|
|
||||||
throw new IllegalStateException("Cannot design low-pass filter for 2 channel synthesizer", fde);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mLowPassFilter = new ComplexFIRFilter2(lowPassFilter, 1.0f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the frequency offset to apply to the incoming samples to mix the desired signal to baseband.
|
* Sets the frequency offset to apply to the incoming samples to mix the desired signal to baseband.
|
||||||
|
*
|
||||||
* @param frequencyOffset in hertz
|
* @param frequencyOffset in hertz
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@@ -126,7 +98,7 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
|
|||||||
public void process(List<ReusableChannelResultsBuffer> channelResultsBuffers,
|
public void process(List<ReusableChannelResultsBuffer> channelResultsBuffers,
|
||||||
ReusableComplexBufferAssembler reusableComplexBufferAssembler)
|
ReusableComplexBufferAssembler reusableComplexBufferAssembler)
|
||||||
{
|
{
|
||||||
for(ReusableChannelResultsBuffer buffer: channelResultsBuffers)
|
for(ReusableChannelResultsBuffer buffer : channelResultsBuffers)
|
||||||
{
|
{
|
||||||
ReusableComplexBuffer channel1 = buffer.getChannel(mChannelOffset1);
|
ReusableComplexBuffer channel1 = buffer.getChannel(mChannelOffset1);
|
||||||
ReusableComplexBuffer channel2 = buffer.getChannel(mChannelOffset2);
|
ReusableComplexBuffer channel2 = buffer.getChannel(mChannelOffset2);
|
||||||
@@ -140,48 +112,11 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
|
|||||||
//Apply offset and frequency correction to center the signal of interest within the synthesized channel
|
//Apply offset and frequency correction to center the signal of interest within the synthesized channel
|
||||||
getFrequencyCorrectionMixer().mixComplex(synthesized.getSamples());
|
getFrequencyCorrectionMixer().mixComplex(synthesized.getSamples());
|
||||||
|
|
||||||
ReusableComplexBuffer lowPassFiltered = mLowPassFilter.filter(synthesized);
|
synthesized.applyGain(getGain());
|
||||||
|
|
||||||
lowPassFiltered.applyGain(getGain());
|
reusableComplexBufferAssembler.receive(synthesized);
|
||||||
|
|
||||||
reusableComplexBufferAssembler.receive(lowPassFiltered);
|
|
||||||
|
|
||||||
buffer.decrementUserCount();
|
buffer.decrementUserCount();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a low-pass filter kernel for the specified sample rate.
|
|
||||||
* @param sampleRate for the channel
|
|
||||||
* @return filter
|
|
||||||
* @throws FilterDesignException if the filter cannot be designed
|
|
||||||
*/
|
|
||||||
private static float[] getLowPassFilter(double sampleRate) throws FilterDesignException
|
|
||||||
{
|
|
||||||
double cutoff = sampleRate / 4.0;
|
|
||||||
int passBandCutoff = (int)(cutoff * 0.92);
|
|
||||||
int stopBandStart = (int)(cutoff * 1.08);
|
|
||||||
|
|
||||||
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
|
||||||
.sampleRate(sampleRate)
|
|
||||||
.gridDensity(16)
|
|
||||||
.oddLength(true)
|
|
||||||
.passBandCutoff((int)passBandCutoff)
|
|
||||||
.passBandAmplitude(1.0)
|
|
||||||
.passBandRipple(0.01)
|
|
||||||
.stopBandStart((int)stopBandStart)
|
|
||||||
.stopBandAmplitude(0.0)
|
|
||||||
.stopBandRipple(0.005) //Approximately 94 dB attenuation
|
|
||||||
.build();
|
|
||||||
|
|
||||||
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
|
|
||||||
|
|
||||||
if(designer.isValid())
|
|
||||||
{
|
|
||||||
return designer.getImpulseResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new FilterDesignException("Couldn't design low-pass filter for sample rate: " + sampleRate +
|
|
||||||
" Pass End:" + passBandCutoff + " Stop Begin:" + stopBandStart);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ public enum PLLGain
|
|||||||
//NOTE: static gain level of 200 produced the best results for releases prior to 0.3.4b2
|
//NOTE: static gain level of 200 produced the best results for releases prior to 0.3.4b2
|
||||||
|
|
||||||
LEVEL_1(150.0, 0, 1),
|
LEVEL_1(150.0, 0, 1),
|
||||||
LEVEL_2(175.0, 2, 4),
|
LEVEL_2(170.0, 2, 4),
|
||||||
LEVEL_3(200.0, 5, 6),
|
LEVEL_3(190.0, 5, 6),
|
||||||
LEVEL_4(200.0, 7, 8),
|
LEVEL_4(200.0, 7, 8),
|
||||||
LEVEL_5(200.0, 9, 10);
|
LEVEL_5(200.0, 9, 10);
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import io.github.dsheirer.source.ISourceEventProcessor;
|
|||||||
import io.github.dsheirer.source.SourceEvent;
|
import io.github.dsheirer.source.SourceEvent;
|
||||||
import io.github.dsheirer.source.SourceException;
|
import io.github.dsheirer.source.SourceException;
|
||||||
import io.github.dsheirer.source.tuner.Tuner;
|
import io.github.dsheirer.source.tuner.Tuner;
|
||||||
|
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
||||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||||
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;
|
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;
|
||||||
@@ -376,10 +377,11 @@ public class ChannelizerViewer2 extends JFrame
|
|||||||
mComplexDecibelConverter.addListener(mSpectrumPanel);
|
mComplexDecibelConverter.addListener(mSpectrumPanel);
|
||||||
|
|
||||||
TunerChannel tunerChannel = new TunerChannel(frequency, bandwidth);
|
TunerChannel tunerChannel = new TunerChannel(frequency, bandwidth);
|
||||||
mSource = mTestTuner.getChannelSourceManager().getSource(tunerChannel, null);
|
mSource = mTestTuner.getChannelSourceManager().getSource(tunerChannel, new ChannelSpecification(50000, 12500, 6000, 7000));
|
||||||
|
|
||||||
if(mSource != null)
|
if(mSource != null)
|
||||||
{
|
{
|
||||||
|
mLog.debug("Channel: " + mSource.getTunerChannel() + " Rate:" + mSource.getSampleRate());
|
||||||
mSource.setListener(new Listener<ReusableComplexBuffer>()
|
mSource.setListener(new Listener<ReusableComplexBuffer>()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -113,7 +113,6 @@ public class DecodeConfigP25Phase1 extends DecodeConfiguration
|
|||||||
@Override
|
@Override
|
||||||
public ChannelSpecification getChannelSpecification()
|
public ChannelSpecification getChannelSpecification()
|
||||||
{
|
{
|
||||||
return new ChannelSpecification(50000.0,
|
return new ChannelSpecification(50000.0, 12500, 5750.0, 6500.0);
|
||||||
12500, 6000.0, 7000.0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import io.github.dsheirer.source.ISourceEventProcessor;
|
|||||||
import io.github.dsheirer.source.SourceEvent;
|
import io.github.dsheirer.source.SourceEvent;
|
||||||
import io.github.dsheirer.source.tuner.TunerEvent.Event;
|
import io.github.dsheirer.source.tuner.TunerEvent.Event;
|
||||||
import io.github.dsheirer.source.tuner.manager.ChannelSourceManager;
|
import io.github.dsheirer.source.tuner.manager.ChannelSourceManager;
|
||||||
import io.github.dsheirer.source.tuner.manager.HeterodyneChannelSourceManager;
|
import io.github.dsheirer.source.tuner.manager.PolyphaseChannelSourceManager;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -51,8 +51,8 @@ public abstract class Tuner implements ISourceEventProcessor
|
|||||||
//Register to receive frequency and sample rate change notifications
|
//Register to receive frequency and sample rate change notifications
|
||||||
mTunerController.addListener(this::process);
|
mTunerController.addListener(this::process);
|
||||||
|
|
||||||
// mChannelSourceManager = new PolyphaseChannelSourceManager(mTunerController);
|
mChannelSourceManager = new PolyphaseChannelSourceManager(mTunerController);
|
||||||
mChannelSourceManager = new HeterodyneChannelSourceManager(mTunerController);
|
// mChannelSourceManager = new HeterodyneChannelSourceManager(mTunerController);
|
||||||
|
|
||||||
//Register to receive channel count change notifications
|
//Register to receive channel count change notifications
|
||||||
mChannelSourceManager.addSourceEventListener(this::process);
|
mChannelSourceManager.addSourceEventListener(this::process);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ package io.github.dsheirer.source.tuner.channel;
|
|||||||
import io.github.dsheirer.dsp.filter.cic.ComplexPrimeCICDecimate;
|
import io.github.dsheirer.dsp.filter.cic.ComplexPrimeCICDecimate;
|
||||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||||
import io.github.dsheirer.dsp.mixer.IOscillator;
|
import io.github.dsheirer.dsp.mixer.IOscillator;
|
||||||
import io.github.dsheirer.dsp.mixer.Oscillator;
|
import io.github.dsheirer.dsp.mixer.LowPhaseNoiseOscillator;
|
||||||
import io.github.dsheirer.sample.IOverflowListener;
|
import io.github.dsheirer.sample.IOverflowListener;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue;
|
import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue;
|
||||||
@@ -81,7 +81,7 @@ public class CICTunerChannelSource extends TunerChannelSource implements Listene
|
|||||||
mTunerFrequency = tunerChannel.getFrequency();
|
mTunerFrequency = tunerChannel.getFrequency();
|
||||||
long frequencyOffset = mTunerFrequency - getTunerChannel().getFrequency();
|
long frequencyOffset = mTunerFrequency - getTunerChannel().getFrequency();
|
||||||
|
|
||||||
mFrequencyCorrectionMixer = new Oscillator(frequencyOffset, sampleRate);
|
mFrequencyCorrectionMixer = new LowPhaseNoiseOscillator(frequencyOffset, sampleRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -294,6 +294,10 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
|
|||||||
{
|
{
|
||||||
mLog.error("Error while sending heartbeat", t);
|
mLog.error("Error while sending heartbeat", t);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!mStopped)
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
processSamples();
|
processSamples();
|
||||||
|
|||||||
@@ -393,11 +393,6 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager
|
|||||||
@Override
|
@Override
|
||||||
public TunerChannelSource getSource(TunerChannel tunerChannel, ChannelSpecification channelSpecification)
|
public TunerChannelSource getSource(TunerChannel tunerChannel, ChannelSpecification channelSpecification)
|
||||||
{
|
{
|
||||||
if(channelSpecification != null)
|
|
||||||
{
|
|
||||||
throw new IllegalArgumentException("Update polyphase source manager to support channel specifications");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(isTunable(tunerChannel))
|
if(isTunable(tunerChannel))
|
||||||
{
|
{
|
||||||
//Get a new set of currently tuned channels
|
//Get a new set of currently tuned channels
|
||||||
@@ -422,7 +417,7 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
//If we're successful to here, allocate the channel
|
//If we're successful to here, allocate the channel
|
||||||
return mPolyphaseChannelManager.getChannel(tunerChannel);
|
return mPolyphaseChannelManager.getChannel(tunerChannel, channelSpecification);
|
||||||
}
|
}
|
||||||
catch(SourceException se)
|
catch(SourceException se)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public class TestTunerController extends TunerController
|
|||||||
|
|
||||||
public static final long MINIMUM_FREQUENCY = 1l;
|
public static final long MINIMUM_FREQUENCY = 1l;
|
||||||
public static final long MAXIMUM_FREQUENCY = 1000000000l;
|
public static final long MAXIMUM_FREQUENCY = 1000000000l;
|
||||||
public static final int SAMPLE_RATE = 50000;
|
public static final int SAMPLE_RATE = 10000000;
|
||||||
public static final int DC_NOISE_BANDWIDTH = 0;
|
public static final int DC_NOISE_BANDWIDTH = 0;
|
||||||
public static final double USABLE_BANDWIDTH_PERCENTAGE = 1.00;
|
public static final double USABLE_BANDWIDTH_PERCENTAGE = 1.00;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user