WIP - identifier framework and restructuring P25 messages to provide

identifiers.
This commit is contained in:
Denny
2018-08-31 04:50:58 -04:00
parent f963d821a1
commit 9acdf22b91
9 changed files with 87 additions and 42 deletions

View File

@@ -760,26 +760,28 @@ public class FilterFactory
* Polyphase M2 synthesizer sync filter. Designed for multiple M2 oversampled channel inputs and * Polyphase M2 synthesizer sync filter. Designed for multiple M2 oversampled channel inputs and
* an M*channel count output. * an M*channel count output.
* *
* @param channelBandwidth per channel * @param channelSampleRate per input channel
* @param channels count * @param channelBandwidth per input channel
* @param channels input count
* @param tapsPerChannel minumum. This may be increased to meet the band edge -6.02dB requirement * @param tapsPerChannel minumum. This may be increased to meet the band edge -6.02dB requirement
* @return filter * @return filter
* @throws FilterDesignException * @throws FilterDesignException
*/ */
public static float[] getSincM2Synthesizer(double channelBandwidth, int channels, int tapsPerChannel) public static float[] getSincM2Synthesizer(double channelSampleRate, double channelBandwidth, int channels,
throws FilterDesignException int tapsPerChannel) throws FilterDesignException
{ {
int filterLength = (channels * tapsPerChannel) - 1; int filterLength = (channels * tapsPerChannel) - 1;
double cutoff = (channelBandwidth * 1.05) / (channelSampleRate * (double)channels);
//Design the prototype synthesizer with 105% of the channel bandwidth produced by the channelizer. //Design the prototype synthesizer with 105% of the channel bandwidth produced by the channelizer.
float[] taps = FilterFactory.getKaiserSinc(filterLength, channelBandwidth * 1.05, 80.0); float[] taps = FilterFactory.getKaiserSinc(filterLength, cutoff, 80.0);
//This is an odd length filter - increase the length by 1 by pre-padding a zero coefficient //This is an odd length filter - increase the length by 1 by pre-padding a zero coefficient
float[] extendedTaps = new float[taps.length + 1]; float[] extendedTaps = new float[taps.length + 1];
System.arraycopy(taps, 0, extendedTaps, 1, taps.length); System.arraycopy(taps, 0, extendedTaps, 1, taps.length);
//Adjust the overall gain of the filter to an objective of 1.0 divided by the number of channels return extendedTaps;
return normalize(extendedTaps, 1.0f);
} }
/** /**
@@ -918,17 +920,18 @@ public class FilterFactory
mLog.debug("Filter Length: " + (extendedTaps.length)); mLog.debug("Filter Length: " + (extendedTaps.length));
mLog.debug("Requested Cutoff Frequency: " + (sampleRate * bandEdge)); mLog.debug("Requested Cutoff Frequency: " + (sampleRate * bandEdge));
mLog.debug("Actual Cutoff Frequency: " + (sampleRate * cutoffFrequency)); mLog.debug("Actual Cutoff Frequency: " + (sampleRate * cutoffFrequency));
mLog.debug("Attenuation at 0.0 OBJECTIVE: " + 0.0); mLog.debug("Attenuation at 0.25 Channels: " + evaluate(taps, bandEdge * 0.25) + "\tFrequency: " + (sampleRate * bandEdge * 0.25) + " [" + (bandEdge * 0.25) + "]");
mLog.debug("Attenuation at 1.0 OBJECTIVE: " + PERFECT_RECONSTRUCTION_GAIN_AT_BAND_EDGE); mLog.debug("Attenuation at 0.50 Channels: " + evaluate(taps, bandEdge * 0.50) + "\tFrequency: " + (sampleRate * bandEdge * 0.50) + " [" + (bandEdge * 0.50) + "]");
mLog.debug("Attenuation at 1.00 Channels: " + evaluate(taps, bandEdge * 1.00) + "\tFrequency: " + (sampleRate * bandEdge * 1.0)); mLog.debug("Attenuation at 0.75 Channels: " + evaluate(taps, bandEdge * 0.75) + "\tFrequency: " + (sampleRate * bandEdge * 0.75) + " [" + (bandEdge * 0.75) + "]");
mLog.debug("Attenuation at 1.25 Channels: " + evaluate(taps, bandEdge * 1.25) + "\tFrequency: " + (sampleRate * bandEdge * 1.25)); mLog.debug("Attenuation OBJECTIVE: " + PERFECT_RECONSTRUCTION_GAIN_AT_BAND_EDGE);
mLog.debug("Attenuation at 1.50 Channels: " + evaluate(taps, bandEdge * 1.50) + "\tFrequency: " + (sampleRate * bandEdge * 1.5)); mLog.debug("Attenuation at 1.00 Channels: " + evaluate(taps, bandEdge * 1.00) + "\tFrequency: " + (sampleRate * bandEdge * 1.0) + " [" + (bandEdge * 1.0) + "]");
mLog.debug("Attenuation at 1.75 Channels: " + evaluate(taps, bandEdge * 1.75) + "\tFrequency: " + (sampleRate * bandEdge * 1.75)); mLog.debug("Attenuation at 1.25 Channels: " + evaluate(taps, bandEdge * 1.25) + "\tFrequency: " + (sampleRate * bandEdge * 1.25) + " [" + (bandEdge * 1.25) + "]");
mLog.debug("Attenuation at 2.00 Channels: " + evaluate(taps, bandEdge * 2.00) + "\tFrequency: " + (sampleRate * bandEdge * 2.0)); mLog.debug("Attenuation at 1.50 Channels: " + evaluate(taps, bandEdge * 1.50) + "\tFrequency: " + (sampleRate * bandEdge * 1.5) + " [" + (bandEdge * 1.5) + "]");
mLog.debug("Attenuation at 1.75 Channels: " + evaluate(taps, bandEdge * 1.75) + "\tFrequency: " + (sampleRate * bandEdge * 1.75) + " [" + (bandEdge * 1.75) + "]");
mLog.debug("Attenuation at 2.00 Channels: " + evaluate(taps, bandEdge * 2.00) + "\tFrequency: " + (sampleRate * bandEdge * 2.0) + " [" + (bandEdge * 2.0) + "]");
} }
//Adjust the overall gain of the filter to an objective of 1.0 times the number of channels return extendedTaps;
return normalize(extendedTaps, 1.0f * (float)channels);
} }
/** /**

View File

@@ -213,12 +213,14 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
switch(indexes.size()) switch(indexes.size())
{ {
case 1: case 1:
return new OneChannelOutputProcessor(mChannelCalculator.getChannelSampleRate(), indexes); return new OneChannelOutputProcessor(mChannelCalculator.getChannelSampleRate(), indexes,
mChannelCalculator.getChannelCount());
case 2: case 2:
try try
{ {
float[] filter = getOutputProcessorFilter(2); float[] filter = getOutputProcessorFilter(2);
return new TwoChannelOutputProcessor(mChannelCalculator.getChannelSampleRate(), indexes, filter); return new TwoChannelOutputProcessor(mChannelCalculator.getChannelSampleRate(), indexes, filter,
mChannelCalculator.getChannelCount());
} }
catch(FilterDesignException fde) catch(FilterDesignException fde)
{ {
@@ -504,8 +506,8 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
if(taps == null) if(taps == null)
{ {
taps = FilterFactory.getSincM2Synthesizer(mChannelCalculator.getChannelBandwidth(), channels, taps = FilterFactory.getSincM2Synthesizer(mChannelCalculator.getChannelSampleRate(),
POLYPHASE_SYNTHESIZER_TAPS_PER_CHANNEL); mChannelCalculator.getChannelBandwidth(), channels, POLYPHASE_SYNTHESIZER_TAPS_PER_CHANNEL);
mOutputProcessorFilters.put(channels, taps); mOutputProcessorFilters.put(channels, taps);
} }

View File

@@ -42,6 +42,7 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
private int mInputChannelCount; private int mInputChannelCount;
private IOscillator mFrequencyCorrectionMixer; private IOscillator mFrequencyCorrectionMixer;
private boolean mFrequencyCorrectionEnabled; private boolean mFrequencyCorrectionEnabled;
private double mGain = 1.0;
/** /**
* Base class for polyphase channelizer output channel processing. Provides built-in frequency translation * Base class for polyphase channelizer output channel processing. Provides built-in frequency translation
@@ -51,9 +52,10 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
* @param sampleRate of the output channel. This is used to match the oscillator's sample rate to the output * @param sampleRate of the output channel. This is used to match the oscillator's sample rate to the output
* channel sample rate for frequency translation/correction. * channel sample rate for frequency translation/correction.
*/ */
public ChannelOutputProcessor(int inputChannelCount, double sampleRate) public ChannelOutputProcessor(int inputChannelCount, double sampleRate, double gain)
{ {
mInputChannelCount = inputChannelCount; mInputChannelCount = inputChannelCount;
mGain = gain;
//TODO: swap this out and use the LowPhaseNoiseOscillator //TODO: swap this out and use the LowPhaseNoiseOscillator
mFrequencyCorrectionMixer = new Oscillator(0, sampleRate); mFrequencyCorrectionMixer = new Oscillator(0, sampleRate);
@@ -62,6 +64,11 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
mChannelResultsQueue = new OverflowableReusableBufferTransferQueue<>((int)(sampleRate * 3), (int)(sampleRate * 0.5)); mChannelResultsQueue = new OverflowableReusableBufferTransferQueue<>((int)(sampleRate * 3), (int)(sampleRate * 0.5));
} }
protected double getGain()
{
return mGain;
}
@Override @Override
public int getPolyphaseChannelIndexCount() public int getPolyphaseChannelIndexCount()
{ {

View File

@@ -38,10 +38,11 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
* *
* @param sampleRate of the output sample stream. * @param sampleRate of the output sample stream.
* @param channelIndexes containing a single channel index. * @param channelIndexes containing a single channel index.
* @param gain value to apply. Typically this is the same as the channelizer's channel count.
*/ */
public OneChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes) public OneChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, double gain)
{ {
super(1, sampleRate); super(1, sampleRate, gain);
setPolyphaseChannelIndices(channelIndexes); setPolyphaseChannelIndices(channelIndexes);
} }
@@ -91,6 +92,8 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
getFrequencyCorrectionMixer().mixComplex(channelBuffer.getSamples()); getFrequencyCorrectionMixer().mixComplex(channelBuffer.getSamples());
} }
channelBuffer.applyGain(getGain());
reusableComplexBufferAssembler.receive(channelBuffer); reusableComplexBufferAssembler.receive(channelBuffer);
} }
catch(IllegalArgumentException iae) catch(IllegalArgumentException iae)

View File

@@ -49,12 +49,13 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
* *
* @param sampleRate of the output sample stream. * @param sampleRate of the output sample stream.
* @param channelIndexes containing two channel indices. * @param channelIndexes containing two channel indices.
* @param gain to apply to output. Typically this is equal to the channelizer's channel count.
*/ */
public TwoChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float[] filter) public TwoChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float[] filter, double gain)
{ {
//Set the frequency correction oscillator to 2 x output sample rate since we'll be correcting the frequency //Set the frequency correction oscillator to 2 x output sample rate since we'll be correcting the frequency
//after synthesizing both input channels //after synthesizing both input channels
super(2, sampleRate); super(2, sampleRate, gain);
setPolyphaseChannelIndices(channelIndexes); setPolyphaseChannelIndices(channelIndexes);
setSynthesisFilter(filter); setSynthesisFilter(filter);
@@ -141,6 +142,8 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
ReusableComplexBuffer lowPassFiltered = mLowPassFilter.filter(synthesized); ReusableComplexBuffer lowPassFiltered = mLowPassFilter.filter(synthesized);
lowPassFiltered.applyGain(getGain());
reusableComplexBufferAssembler.receive(lowPassFiltered); reusableComplexBufferAssembler.receive(lowPassFiltered);
buffer.decrementUserCount(); buffer.decrementUserCount();

View File

@@ -1,7 +1,7 @@
package io.github.dsheirer.dsp.filter.design; package io.github.dsheirer.dsp.filter.design;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification; import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
import javafx.application.Application; import javafx.application.Application;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.stage.Stage; import javafx.stage.Stage;
@@ -81,28 +81,39 @@ public class FilterViewer extends Application
// catch(FilterDesignException fde) // catch(FilterDesignException fde)
// { // {
// mLog.error("Error"); // mLog.error("Error");
// }
// try
// {
// RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
//
// if(designer.isValid())
// {
// taps = designer.getImpulseResponse();
// }
// }
// catch(FilterDesignException fde)
// {
// mLog.error("Filter design error", fde);
// }
//
// if(taps == null)
// {
// throw new IllegalStateException("Couldn't design filter");
// } // }
try try
{ {
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification); // taps = FilterFactory.getSincM2Synthesizer(0.25, 2, 9);
// taps = FilterFactory.getSincM2Channelizer(12500.0, 2, 9, true);
taps = FilterFactory.getSincM2Synthesizer( 25000.0, 12500.0, 2, 9);
if(designer.isValid())
{
taps = designer.getImpulseResponse();
}
} }
catch(FilterDesignException fde) catch(FilterDesignException e)
{ {
mLog.error("Filter design error", fde); e.printStackTrace();
} }
if(taps == null)
{
throw new IllegalStateException("Couldn't design filter");
}
return taps; return taps;
} }
} }

View File

@@ -281,7 +281,7 @@ public class SynthesizerViewer extends JFrame
{ {
try try
{ {
float[] taps = FilterFactory.getSincM2Synthesizer(12500.0, 2, 12); float[] taps = FilterFactory.getSincM2Synthesizer( 25000.0, 12500.0, 2, 12);
mSynthesizer = new TwoChannelSynthesizerM2(taps); mSynthesizer = new TwoChannelSynthesizerM2(taps);
} }
catch(FilterDesignException fde) catch(FilterDesignException fde)

View File

@@ -56,4 +56,17 @@ public class ReusableComplexBuffer extends ReusableFloatBuffer
{ {
return getSamples().length / 2; return getSamples().length / 2;
} }
/**
* Applies the gain value to the samples contained in this buffer
*/
public void applyGain(double gain)
{
float[] samples = getSamples();
for(int x = 0; x < samples.length; x++)
{
samples[x] *= gain;
}
}
} }

View File

@@ -416,14 +416,17 @@ public class USBTransferProcessor implements TransferCallback
while(transfer != null) while(transfer != null)
{ {
if(mRunning.get() && mComplexBufferListener != null) if(mRunning.get())
{ {
ByteBuffer nativeBuffer = transfer.buffer(); ByteBuffer nativeBuffer = transfer.buffer();
ReusableComplexBuffer reusableComplexBuffer = ReusableComplexBuffer reusableComplexBuffer =
mNativeBufferConverter.convert(nativeBuffer, transfer.actualLength()); mNativeBufferConverter.convert(nativeBuffer, transfer.actualLength());
mComplexBufferListener.receive(reusableComplexBuffer); if(mComplexBufferListener != null)
{
mComplexBufferListener.receive(reusableComplexBuffer);
}
} }
transfer.buffer().rewind(); transfer.buffer().rewind();