diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java index 8aee60f4..1645a77a 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -15,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.dsp.filter.channelizer; @@ -40,6 +39,7 @@ public class PolyphaseChannelSource extends TunerChannelSource { // private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class); + private static final int PROCESSED_BUFFER_SAMPLE_SIZE = 2048; private ReusableComplexBufferAssembler mReusableComplexBufferAssembler; private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor; private IPolyphaseChannelOutputProcessor mReplacementPolyphaseChannelOutputProcessor; @@ -71,7 +71,7 @@ public class PolyphaseChannelSource extends TunerChannelSource super(producerSourceEventListener, tunerChannel); mPolyphaseChannelOutputProcessor = outputProcessor; mChannelSampleRate = channelSampleRate; - mReusableComplexBufferAssembler = new ReusableComplexBufferAssembler(2500, mChannelSampleRate); + mReusableComplexBufferAssembler = new ReusableComplexBufferAssembler(PROCESSED_BUFFER_SAMPLE_SIZE, mChannelSampleRate); float[] filterCoefficients = getLowPassFilter(channelSampleRate, channelSpecification.getPassFrequency(), channelSpecification.getStopFrequency()); diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX0Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX0Filter.java new file mode 100644 index 00000000..bd686ddd --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX0Filter.java @@ -0,0 +1,40 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.filter.decimate; + +import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; + +/** + * No decimation filter. + */ +public class ComplexDecimateX0Filter implements IComplexDecimationFilter +{ + @Override + public float[] decimateComplex(float[] samples) + { + return samples; + } + + @Override + public ReusableComplexBuffer decimate(ReusableComplexBuffer buffer) + { + return buffer; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/DecimationFilterFactory.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/DecimationFilterFactory.java index 85e491ef..d8d5a750 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/DecimationFilterFactory.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/DecimationFilterFactory.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2021 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -24,7 +24,7 @@ package io.github.dsheirer.dsp.filter.decimate; */ public class DecimationFilterFactory { - private static final int[] SUPPORTED_RATES = new int[]{2,4,8,16,32,64,128,256,512,1024}; + private static final int[] SUPPORTED_RATES = new int[]{0,2,4,8,16,32,64,128,256,512,1024}; /** * Creates a real-valued decimation filter for float array sample buffers providing greater than 100 dB of @@ -37,6 +37,8 @@ public class DecimationFilterFactory { switch(decimationRate) { + case 0: + return new RealDecimateX0Filter(); case 2: return new RealDecimateX2Filter(); case 4: @@ -74,6 +76,8 @@ public class DecimationFilterFactory { switch(decimationRate) { + case 0: + return new ComplexDecimateX0Filter(); case 2: return new ComplexDecimateX2Filter(); case 4: diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/IComplexDecimationFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/IComplexDecimationFilter.java index 0c6c544e..1779ece9 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/IComplexDecimationFilter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/IComplexDecimationFilter.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2021 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -19,10 +19,24 @@ package io.github.dsheirer.dsp.filter.decimate; +import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; + /** * Interface for a decimation filter for float array sample buffers */ public interface IComplexDecimationFilter { + /** + * Decimates an array of float complex samples + * @param samples to decimate + * @return decimated samples + */ float[] decimateComplex(float[] samples); + + /** + * Decimates an buffer of float complex samples + * @param buffer to decimate + * @return decimated samples + */ + ReusableComplexBuffer decimate(ReusableComplexBuffer buffer); } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX0Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX0Filter.java new file mode 100644 index 00000000..84b3620d --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX0Filter.java @@ -0,0 +1,32 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.filter.decimate; + +/** + * No decimation filter. + */ +public class RealDecimateX0Filter implements IRealDecimationFilter +{ + @Override + public float[] decimateReal(float[] samples) + { + return samples; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/complex/ComplexHalfBandDecimationFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/complex/ComplexHalfBandDecimationFilter.java index 8b308a95..b13ae60c 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/halfband/complex/ComplexHalfBandDecimationFilter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/complex/ComplexHalfBandDecimationFilter.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2021 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -20,6 +20,8 @@ package io.github.dsheirer.dsp.filter.halfband.complex; import io.github.dsheirer.dsp.filter.decimate.IComplexDecimationFilter; +import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; /** * Complex half-band filter that processes samples on a per-array basis, versus a per-sample basis. @@ -35,6 +37,7 @@ public class ComplexHalfBandDecimationFilter implements IComplexDecimationFilter private int mCoefficientsLengthMinus2; private int mBufferPointer; private int mHalf; + private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("half-band decimation filter"); /** * Constructs a complex sample half-band decimate x2 filter using the specified filter coefficients. @@ -118,4 +121,17 @@ public class ComplexHalfBandDecimationFilter implements IComplexDecimationFilter return filtered; } + + /** + * Decimates the complex samples and returns a buffer of decimated samples. + * @param buffer to decimate + * @return decimated buffer. + */ + @Override + public ReusableComplexBuffer decimate(ReusableComplexBuffer buffer) + { + float[] decimated = decimateComplex(buffer.getSamples()); + buffer.decrementUserCount(); + return mReusableComplexBufferQueue.getBuffer(decimated, buffer.getTimestamp()); + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/nbfm/NBFMDecoder.java b/src/main/java/io/github/dsheirer/module/decode/nbfm/NBFMDecoder.java index 4676744c..d2f82592 100644 --- a/src/main/java/io/github/dsheirer/module/decode/nbfm/NBFMDecoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/nbfm/NBFMDecoder.java @@ -1,20 +1,21 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014,2015 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 - ******************************************************************************/ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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 + * **************************************************************************** + */ package io.github.dsheirer.module.decode.nbfm; import io.github.dsheirer.channel.state.DecoderStateEvent; @@ -22,6 +23,8 @@ import io.github.dsheirer.channel.state.IDecoderStateEventProvider; import io.github.dsheirer.channel.state.State; import io.github.dsheirer.dsp.filter.FilterFactory; import io.github.dsheirer.dsp.filter.Window; +import io.github.dsheirer.dsp.filter.decimate.DecimationFilterFactory; +import io.github.dsheirer.dsp.filter.decimate.IComplexDecimationFilter; 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; @@ -49,11 +52,12 @@ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, { private final static Logger mLog = LoggerFactory.getLogger(NBFMDecoder.class); private static final double DEMODULATED_AUDIO_SAMPLE_RATE = 8000.0; - private static final double POWER_SQUELCH_ALPHA_DECAY = 0.0002; + private static final double POWER_SQUELCH_ALPHA_DECAY = 0.0004; private static final double POWER_SQUELCH_THRESHOLD_DB = -78.0; private static final int POWER_SQUELCH_RAMP = 4; private ComplexFIRFilter2 mIQFilter; + private IComplexDecimationFilter mDecimationFilter; private SquelchingFMDemodulator mDemodulator = new SquelchingFMDemodulator(POWER_SQUELCH_ALPHA_DECAY, POWER_SQUELCH_THRESHOLD_DB, POWER_SQUELCH_RAMP); private RealResampler mResampler; @@ -131,7 +135,9 @@ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, "event before it can process complex sample buffers"); } - ReusableComplexBuffer basebandFilteredBuffer = mIQFilter.filter(reusableComplexBuffer); + ReusableComplexBuffer decimatedBuffer = mDecimationFilter.decimate(reusableComplexBuffer); + ReusableComplexBuffer basebandFilteredBuffer = mIQFilter.filter(decimatedBuffer); + ReusableFloatBuffer demodulatedBuffer = mDemodulator.demodulate(basebandFilteredBuffer); if(mResampler != null) @@ -268,11 +274,31 @@ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, double sampleRate = sourceEvent.getValue().doubleValue(); - if((sampleRate < (2.0 * mChannelBandwidth))) + int decimationRate = 0; + double decimatedSampleRate = sampleRate; + + if(sampleRate / 2 > mChannelBandwidth) + { + decimationRate = 2; + + while(sampleRate / decimationRate / 2 > mChannelBandwidth) + { + decimationRate *= 2; + } + } + + if(decimationRate > 0) + { + decimatedSampleRate /= decimationRate; + } + + mDecimationFilter = DecimationFilterFactory.getComplexDecimationFilter(decimationRate); + + if((decimatedSampleRate < (2.0 * mChannelBandwidth))) { throw new IllegalStateException("FM Demodulator with channel bandwidth [" + mChannelBandwidth + "] requires a channel sample rate of [" + (2.0 * mChannelBandwidth + "] - sample rate of [" + - sampleRate + "] is not supported")); + decimatedSampleRate + "] is not supported")); } int passBandStop = (int)(mChannelBandwidth * .8); @@ -281,7 +307,7 @@ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, float[] filterTaps = null; FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder() - .sampleRate(sampleRate) + .sampleRate(decimatedSampleRate * 2) .gridDensity(16) .oddLength(true) .passBandCutoff(passBandStop) @@ -300,18 +326,21 @@ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, { mLog.error("Couldn't design FM demodulator remez filter for sample rate [" + sampleRate + "] pass frequency [" + passBandStop + "] and stop frequency [" + stopBandStart + - "] - using sinc filter"); + "] - will proceed using sinc (low-pass) filter"); } if(filterTaps == null) { - filterTaps = FilterFactory.getLowPass(sampleRate, passBandStop, stopBandStart, 60, + mLog.info("Unable to use remez filter designer for sample rate [" + decimatedSampleRate + + "] pass band stop [" + passBandStop + + "] and stop band start [" + stopBandStart + "] - will proceed using simple low pass filter design"); + filterTaps = FilterFactory.getLowPass(decimatedSampleRate, passBandStop, stopBandStart, 60, Window.WindowType.HAMMING, true); } mIQFilter = new ComplexFIRFilter2(filterTaps); - mResampler = new RealResampler(sampleRate, mOutputSampleRate, 2000, 1000); + mResampler = new RealResampler(decimatedSampleRate, mOutputSampleRate, 2000, 1000); mResampler.setListener(reusableFloatBuffer -> { diff --git a/src/main/java/io/github/dsheirer/sample/buffer/AbstractReusableBufferQueue.java b/src/main/java/io/github/dsheirer/sample/buffer/AbstractReusableBufferQueue.java index 2f5d38b3..dd07dbab 100644 --- a/src/main/java/io/github/dsheirer/sample/buffer/AbstractReusableBufferQueue.java +++ b/src/main/java/io/github/dsheirer/sample/buffer/AbstractReusableBufferQueue.java @@ -1,18 +1,21 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.sample.buffer; import org.slf4j.Logger; @@ -50,6 +53,14 @@ public abstract class AbstractReusableBufferQueue - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.sample.buffer; import org.slf4j.Logger; @@ -61,4 +64,31 @@ public class ReusableComplexBufferQueue extends AbstractReusableBufferQueue