diff --git a/src/main/java/io/github/dsheirer/dsp/filter/FilterFactory.java b/src/main/java/io/github/dsheirer/dsp/filter/FilterFactory.java index 989372fb..49fcdc37 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/FilterFactory.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/FilterFactory.java @@ -760,26 +760,28 @@ public class FilterFactory * Polyphase M2 synthesizer sync filter. Designed for multiple M2 oversampled channel inputs and * an M*channel count output. * - * @param channelBandwidth per channel - * @param channels count + * @param channelSampleRate per input channel + * @param channelBandwidth per input channel + * @param channels input count * @param tapsPerChannel minumum. This may be increased to meet the band edge -6.02dB requirement * @return filter * @throws FilterDesignException */ - public static float[] getSincM2Synthesizer(double channelBandwidth, int channels, int tapsPerChannel) - throws FilterDesignException + public static float[] getSincM2Synthesizer(double channelSampleRate, double channelBandwidth, int channels, + int tapsPerChannel) throws FilterDesignException { 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. - 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 float[] extendedTaps = new float[taps.length + 1]; 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 normalize(extendedTaps, 1.0f); + return extendedTaps; } /** @@ -918,17 +920,18 @@ public class FilterFactory mLog.debug("Filter Length: " + (extendedTaps.length)); mLog.debug("Requested Cutoff Frequency: " + (sampleRate * bandEdge)); mLog.debug("Actual Cutoff Frequency: " + (sampleRate * cutoffFrequency)); - mLog.debug("Attenuation at 0.0 OBJECTIVE: " + 0.0); - mLog.debug("Attenuation at 1.0 OBJECTIVE: " + PERFECT_RECONSTRUCTION_GAIN_AT_BAND_EDGE); - mLog.debug("Attenuation at 1.00 Channels: " + evaluate(taps, bandEdge * 1.00) + "\tFrequency: " + (sampleRate * bandEdge * 1.0)); - mLog.debug("Attenuation at 1.25 Channels: " + evaluate(taps, bandEdge * 1.25) + "\tFrequency: " + (sampleRate * bandEdge * 1.25)); - mLog.debug("Attenuation at 1.50 Channels: " + evaluate(taps, bandEdge * 1.50) + "\tFrequency: " + (sampleRate * bandEdge * 1.5)); - mLog.debug("Attenuation at 1.75 Channels: " + evaluate(taps, bandEdge * 1.75) + "\tFrequency: " + (sampleRate * bandEdge * 1.75)); - mLog.debug("Attenuation at 2.00 Channels: " + evaluate(taps, bandEdge * 2.00) + "\tFrequency: " + (sampleRate * bandEdge * 2.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 0.50 Channels: " + evaluate(taps, bandEdge * 0.50) + "\tFrequency: " + (sampleRate * bandEdge * 0.50) + " [" + (bandEdge * 0.50) + "]"); + mLog.debug("Attenuation at 0.75 Channels: " + evaluate(taps, bandEdge * 0.75) + "\tFrequency: " + (sampleRate * bandEdge * 0.75) + " [" + (bandEdge * 0.75) + "]"); + mLog.debug("Attenuation OBJECTIVE: " + PERFECT_RECONSTRUCTION_GAIN_AT_BAND_EDGE); + 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.25 Channels: " + evaluate(taps, bandEdge * 1.25) + "\tFrequency: " + (sampleRate * bandEdge * 1.25) + " [" + (bandEdge * 1.25) + "]"); + 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 normalize(extendedTaps, 1.0f * (float)channels); + return extendedTaps; } /** diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java index eeadaadc..74cfda8d 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java @@ -213,12 +213,14 @@ public class PolyphaseChannelManager implements ISourceEventProcessor switch(indexes.size()) { case 1: - return new OneChannelOutputProcessor(mChannelCalculator.getChannelSampleRate(), indexes); + return new OneChannelOutputProcessor(mChannelCalculator.getChannelSampleRate(), indexes, + mChannelCalculator.getChannelCount()); case 2: try { float[] filter = getOutputProcessorFilter(2); - return new TwoChannelOutputProcessor(mChannelCalculator.getChannelSampleRate(), indexes, filter); + return new TwoChannelOutputProcessor(mChannelCalculator.getChannelSampleRate(), indexes, filter, + mChannelCalculator.getChannelCount()); } catch(FilterDesignException fde) { @@ -504,8 +506,8 @@ public class PolyphaseChannelManager implements ISourceEventProcessor if(taps == null) { - taps = FilterFactory.getSincM2Synthesizer(mChannelCalculator.getChannelBandwidth(), channels, - POLYPHASE_SYNTHESIZER_TAPS_PER_CHANNEL); + taps = FilterFactory.getSincM2Synthesizer(mChannelCalculator.getChannelSampleRate(), + mChannelCalculator.getChannelBandwidth(), channels, POLYPHASE_SYNTHESIZER_TAPS_PER_CHANNEL); mOutputProcessorFilters.put(channels, taps); } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/ChannelOutputProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/ChannelOutputProcessor.java index ec27da9a..335e957d 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/ChannelOutputProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/ChannelOutputProcessor.java @@ -42,6 +42,7 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP private int mInputChannelCount; private IOscillator mFrequencyCorrectionMixer; private boolean mFrequencyCorrectionEnabled; + private double mGain = 1.0; /** * 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 * channel sample rate for frequency translation/correction. */ - public ChannelOutputProcessor(int inputChannelCount, double sampleRate) + public ChannelOutputProcessor(int inputChannelCount, double sampleRate, double gain) { mInputChannelCount = inputChannelCount; + mGain = gain; //TODO: swap this out and use the LowPhaseNoiseOscillator 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)); } + protected double getGain() + { + return mGain; + } + @Override public int getPolyphaseChannelIndexCount() { diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelOutputProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelOutputProcessor.java index ece7eedd..4e7b57e0 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelOutputProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelOutputProcessor.java @@ -38,10 +38,11 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor * * @param sampleRate of the output sample stream. * @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 channelIndexes) + public OneChannelOutputProcessor(double sampleRate, List channelIndexes, double gain) { - super(1, sampleRate); + super(1, sampleRate, gain); setPolyphaseChannelIndices(channelIndexes); } @@ -91,6 +92,8 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor getFrequencyCorrectionMixer().mixComplex(channelBuffer.getSamples()); } + channelBuffer.applyGain(getGain()); + reusableComplexBufferAssembler.receive(channelBuffer); } catch(IllegalArgumentException iae) diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java index 34cca3a1..6a8090f0 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java @@ -49,12 +49,13 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor * * @param sampleRate of the output sample stream. * @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 channelIndexes, float[] filter) + public TwoChannelOutputProcessor(double sampleRate, List channelIndexes, float[] filter, double gain) { //Set the frequency correction oscillator to 2 x output sample rate since we'll be correcting the frequency //after synthesizing both input channels - super(2, sampleRate); + super(2, sampleRate, gain); setPolyphaseChannelIndices(channelIndexes); setSynthesisFilter(filter); @@ -141,6 +142,8 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor ReusableComplexBuffer lowPassFiltered = mLowPassFilter.filter(synthesized); + lowPassFiltered.applyGain(getGain()); + reusableComplexBufferAssembler.receive(lowPassFiltered); buffer.decrementUserCount(); diff --git a/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java b/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java index 9cf838eb..242d0c5e 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java @@ -1,7 +1,7 @@ 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.remez.RemezFIRFilterDesigner; import javafx.application.Application; import javafx.scene.Scene; import javafx.stage.Stage; @@ -81,28 +81,39 @@ public class FilterViewer extends Application // catch(FilterDesignException fde) // { // 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 { - 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; } } diff --git a/src/main/java/io/github/dsheirer/gui/channelizer/SynthesizerViewer.java b/src/main/java/io/github/dsheirer/gui/channelizer/SynthesizerViewer.java index bd23790d..39b3b3c4 100644 --- a/src/main/java/io/github/dsheirer/gui/channelizer/SynthesizerViewer.java +++ b/src/main/java/io/github/dsheirer/gui/channelizer/SynthesizerViewer.java @@ -281,7 +281,7 @@ public class SynthesizerViewer extends JFrame { try { - float[] taps = FilterFactory.getSincM2Synthesizer(12500.0, 2, 12); + float[] taps = FilterFactory.getSincM2Synthesizer( 25000.0, 12500.0, 2, 12); mSynthesizer = new TwoChannelSynthesizerM2(taps); } catch(FilterDesignException fde) diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBuffer.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBuffer.java index 80455474..49df3b1e 100644 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBuffer.java +++ b/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBuffer.java @@ -56,4 +56,17 @@ public class ReusableComplexBuffer extends ReusableFloatBuffer { 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; + } + } } diff --git a/src/main/java/io/github/dsheirer/source/tuner/usb/USBTransferProcessor.java b/src/main/java/io/github/dsheirer/source/tuner/usb/USBTransferProcessor.java index 7de259f8..94ee1924 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/usb/USBTransferProcessor.java +++ b/src/main/java/io/github/dsheirer/source/tuner/usb/USBTransferProcessor.java @@ -416,14 +416,17 @@ public class USBTransferProcessor implements TransferCallback while(transfer != null) { - if(mRunning.get() && mComplexBufferListener != null) + if(mRunning.get()) { ByteBuffer nativeBuffer = transfer.buffer(); ReusableComplexBuffer reusableComplexBuffer = mNativeBufferConverter.convert(nativeBuffer, transfer.actualLength()); - mComplexBufferListener.receive(reusableComplexBuffer); + if(mComplexBufferListener != null) + { + mComplexBufferListener.receive(reusableComplexBuffer); + } } transfer.buffer().rewind();