From 8642def77ef30f49359a9ce19ff8238c7983438e Mon Sep 17 00:00:00 2001 From: Denny Date: Wed, 5 Sep 2018 06:31:39 -0400 Subject: [PATCH] Cleanup, renaming and removing legacy code. --- .../dsheirer/dsp/filter/FilterFactory.java | 10 - .../filter/cic/ComplexPrimeCICDecimate.java | 226 +++++--- .../filter/cic/ComplexPrimeCICDecimate2.java | 493 ------------------ .../github/dsheirer/source/tuner/Tuner.java | 2 +- .../tuner/channel/CICTunerChannelSource.java | 234 +++++---- .../tuner/channel/CICTunerChannelSource2.java | 239 --------- .../HeterodyneChannelSourceManager.java | 22 +- ...ava => PolyphaseChannelSourceManager.java} | 8 +- 8 files changed, 297 insertions(+), 937 deletions(-) delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/cic/ComplexPrimeCICDecimate2.java delete mode 100644 src/main/java/io/github/dsheirer/source/tuner/channel/CICTunerChannelSource2.java rename src/main/java/io/github/dsheirer/source/tuner/manager/{TunerChannelSourceManager.java => PolyphaseChannelSourceManager.java} (98%) 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 53ae57f3..cab8d1b7 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/FilterFactory.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/FilterFactory.java @@ -22,7 +22,6 @@ ******************************************************************************/ package io.github.dsheirer.dsp.filter; -import io.github.dsheirer.dsp.filter.cic.ComplexPrimeCICDecimate; import io.github.dsheirer.dsp.filter.design.FilterDesignException; import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification; import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner; @@ -358,15 +357,6 @@ public class FilterFactory return (int)(Math.round((double)attenuation / (22.0d * frequency))); } - public static ComplexPrimeCICDecimate getDecimationFilter(int sampleRate, int decimatedRate, int order, - int passFrequency, int attenuation, Window.WindowType windowType) - { - int decimationRate = (int)(sampleRate / decimatedRate); - - return new ComplexPrimeCICDecimate(decimationRate, order, - passFrequency, attenuation, windowType); - } - /** * Determines decimation rate(s) for a polyphase decimation filter. * diff --git a/src/main/java/io/github/dsheirer/dsp/filter/cic/ComplexPrimeCICDecimate.java b/src/main/java/io/github/dsheirer/dsp/filter/cic/ComplexPrimeCICDecimate.java index 54c2db59..768432c7 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/cic/ComplexPrimeCICDecimate.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/cic/ComplexPrimeCICDecimate.java @@ -15,11 +15,10 @@ ******************************************************************************/ package io.github.dsheirer.dsp.filter.cic; -import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Filters; -import io.github.dsheirer.dsp.filter.Window; +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.halfband.complex.ComplexHalfBandFilter; +import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler; @@ -28,52 +27,93 @@ import io.github.dsheirer.sample.decimator.ComplexDecimator; import org.apache.commons.lang3.Validate; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +/** + * Non-Recursive Prime-Factor CIC Filter. + * + * Implements the CIC filter described in Understanding Digital Signal Processing, 3e, Lyons, on page 769. This + * filter is comprised of multiple decimating stages each with a prime factor decimation rate. Multiple stages are + * cascaded to achieve the overall decimation rate. + * + * This filter currently supports a maximum decimation rate of 2801. Higher decimation rates can be added by + * adding additional prime factors to the PRIMES array. + */ public class ComplexPrimeCICDecimate implements Listener { +// private final static Logger mLog = LoggerFactory.getLogger(ComplexPrimeCICDecimate.class); + + /** + * PRIME numbers to use for decimation stage sizing. This list contains values that should + * support input sample rates up to 70 MHz with an output channel rate of at least 25 kHz. + */ public static int[] PRIMES = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, - 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691}; + 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, + 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, + 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, + 967, 971, 977, 983, 991, 997, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, + 1061, 1063, 1069, 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, + 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, + 1283, 1289, 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399, + 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, + 1493, 1499, 1511, 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, + 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, 1669, 1693, 1697, 1699, 1709, + 1721, 1723, 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, 1823, 1831, + 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951, + 1973, 1979, 1987, 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, + 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179, + 2203, 2207, 2213, 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, 2293, 2297, + 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, 2371, 2377, 2381, 2383, 2389, 2393, 2399, + 2411, 2417, 2423, 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, 2539, 2543, + 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, 2621, 2633, 2647, 2657, 2659, 2663, 2671, + 2677, 2683, 2687, 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753, + 2767, 2777, 2789, 2791, 2797, 2801 + }; - private ArrayList mDecimatingStages = - new ArrayList(); + private static Map sLowPassFilters = new HashMap(); + private static Map> sPrimeFactors = new HashMap(); + private List mDecimatingStages = new ArrayList(); private DecimatingStage mFirstDecimatingStage; - private Output mOutput; /** - * Non-Recursive Prime-Factor CIC Filter with float sample array inputs and - * decimated, single paired i/q float sample output. + * Constructs a new decimation filter. * - * Implements the CIC filter described in Understanding Digital Signal - * Processing, 3e, Lyons, on page 769. This filter is comprised of multiple - * decimating stages each with a prime factor decimation rate. Multiple - * stages are cascaded to achieve the overall decimation rate. + * An example setup for a 2.4 MHz input sample rate and 25 kHz output channel rate with a desired 12.5 kHz channel + * pass band might be: + * sampleRate = 2,400,000.0 + * decimation = 96 + * passFrequency = 6250 (1/4 of the channel rate) + * stopFrequency = 7000 * - * This filter supports a maximum decimation rate of 700. This filter can - * be adapted to higher decimation rates by adding additional prime factors - * to the PRIMES array. + * Note: the transition bandwidth (stop - start) directly impacts the size of the low-pass cleanup filter, + * where smaller transition bandwidth creates a longer filter that requires more processing effort. * + * @param sampleRate of the input sample stream * @param decimation - overall decimation rate - * @param order - filter order + * @param passFrequency for the final cleanup filter + * @param stopFrequency for the final cleanup filter + * @throws FilterDesignException if a final low-pass cleanup filter cannot be created for the output channel rate + * and specified pass/stop frequencies. */ - public ComplexPrimeCICDecimate(int decimation, int order, - int passFrequency, int attenuation, Window.WindowType windowType) + public ComplexPrimeCICDecimate(double sampleRate, int decimation, int passFrequency, int stopFrequency) + throws FilterDesignException { - Validate.isTrue(decimation <= 700); - + Validate.isTrue(decimation <= PRIMES[PRIMES.length - 1]); List stageSizes = getPrimeFactors(decimation); for(int x = 0; x < stageSizes.size(); x++) { - DecimatingStage stage = new DecimatingStage(stageSizes.get(x), order); + DecimatingStage stage = new DecimatingStage(stageSizes.get(x), 1); mDecimatingStages.add(stage); @@ -89,10 +129,11 @@ public class ComplexPrimeCICDecimate implements Listener } } - mOutput = new Output(48000, passFrequency, attenuation, windowType); + double channelRate = sampleRate / (double)decimation; - mDecimatingStages.get(mDecimatingStages.size() - 1) - .setListener(mOutput); + mOutput = new Output(channelRate, passFrequency, stopFrequency); + + mDecimatingStages.get(mDecimatingStages.size() - 1).setListener(mOutput); } public void dispose() @@ -127,16 +168,26 @@ public class ComplexPrimeCICDecimate implements Listener } /** - * Calculates the prime factors of the decimation rate up to a maximum - * decimation rate of 700. If you wish to have decimation rates higher - * than 700, then add additional prime factors to the PRIMES array. + * Calculates the prime factors of the decimation rate. If you wish to have decimation rates higher than the + * highest rate listed in the PRIMES array, then add additional prime factors to the PRIMES array. * * @param decimation - integral decimation rate * @return - ordered list (smallest to largest) of prime factors */ public static List getPrimeFactors(int decimation) { - ArrayList stages = new ArrayList(); + if(decimation > PRIMES[PRIMES.length - 1]) + { + throw new IllegalArgumentException("Decimation rates higher than [" + PRIMES[PRIMES.length - 1] + + "] are not supported. Update the PRIMES array to add support for higher decimation rates"); + } + + if(sPrimeFactors.containsKey(decimation)) + { + return sPrimeFactors.get(decimation); + } + + List primeFactors = new ArrayList(); int pointer = 0; @@ -146,8 +197,7 @@ public class ComplexPrimeCICDecimate implements Listener if(decimation % prime == 0) { - stages.add(prime); - + primeFactors.add(prime); decimation /= prime; } else @@ -156,7 +206,9 @@ public class ComplexPrimeCICDecimate implements Listener } } - return stages; + sPrimeFactors.put(decimation, primeFactors); + + return primeFactors; } /** @@ -185,7 +237,7 @@ public class ComplexPrimeCICDecimate implements Listener */ public class DecimatingStage implements ComplexSampleListener { - private ArrayList mStages = new ArrayList(); + private List mStages = new ArrayList(); private Stage mFirstStage; private ComplexDecimator mDecimator; @@ -217,7 +269,6 @@ public class ComplexPrimeCICDecimate implements Listener } mDecimator = new ComplexDecimator(size); - mStages.get(mStages.size() - 1).setListener(mDecimator); } @@ -248,9 +299,8 @@ public class ComplexPrimeCICDecimate implements Listener } /** - * Single non-decimating CIC stage component. Uses a circular buffer and a - * running average internally to implement the stage so that stage size has - * essentially no impact on the computational requirements of the stage + * Single non-decimating CIC stage component. Uses a circular buffer and a running average internally to implement + * the stage so that stage size has essentially no impact on the computational requirements of the stage */ public class Stage implements ComplexSampleListener { @@ -267,7 +317,7 @@ public class ComplexPrimeCICDecimate implements Listener protected float mGain; - public Stage() + protected Stage() { } @@ -278,7 +328,8 @@ public class ComplexPrimeCICDecimate implements Listener mISamples = new float[mSize]; mQSamples = new float[mSize]; - mGain = 1.0f / (float)size; + float baseGain = 1.0f / (float)size; + mGain = baseGain * (1.0f / (1.0f - baseGain)); } public void dispose() @@ -288,7 +339,7 @@ public class ComplexPrimeCICDecimate implements Listener public void receive(float i, float q) { - /* Subtract the oldest sample and add back in the newest */ + /* Subtract the oldest sample and add in the newest sample */ mISum = mISum - mISamples[mSamplePointer] + i; mQSum = mQSum - mQSamples[mSamplePointer] + q; @@ -316,30 +367,24 @@ public class ComplexPrimeCICDecimate implements Listener } /** - * Size 2 stage that removes the unnecessary circular buffer management for - * a two-stage. + * Size 2 stage that eliminates the unnecessary circular buffer management for a two-stage. */ public class TwoStage extends Stage { public TwoStage() { - super(); - mGain = 0.5f; } public void receive(float i, float q) { - float iSum = mISum + i; - float qSum = mQSum + q; + if(mListener != null) + { + mListener.receive(((mISum + i) * mGain), ((mQSum + q) * mGain)); + } mISum = i; mQSum = q; - - if(mListener != null) - { - mListener.receive((iSum * mGain), (qSum * mGain)); - } } } @@ -350,31 +395,29 @@ public class ComplexPrimeCICDecimate implements Listener */ public class Output implements ComplexSampleListener { - /* Decimated output buffers will contain 1024 samples */ - private ReusableComplexBufferAssembler mAssembler = new ReusableComplexBufferAssembler(2048, 48000.0); - - private ComplexFIRFilter2 mCleanupFilter; - private ComplexHalfBandFilter mHalfBandFilter = new ComplexHalfBandFilter( - Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false); + /* Decimated output buffers will contain 1024 complex samples */ + private ReusableComplexBufferAssembler mBufferAssembler; + private ComplexFIRFilter2 mLowPassFilter; private Listener mReusableComplexBufferListener; - public Output(int outputSampleRate, int passFrequency, int attenuation, - Window.WindowType windowType) + public Output(double outputSampleRate, int passFrequency, int stopFrequency) throws FilterDesignException { - mCleanupFilter = new ComplexFIRFilter2(FilterFactory - .getCICCleanupFilter(outputSampleRate, passFrequency, attenuation, windowType), 0.4f); + mBufferAssembler = new ReusableComplexBufferAssembler(2400, outputSampleRate); - //Bypassing the CIC cleanup filter for now - mAssembler.setListener(new Listener() + //This may throw an exception if we can't design a filter for the sample rate and pass/stop frequencies + float[] filterCoefficients = getLowPassFilter(outputSampleRate, passFrequency, stopFrequency); + mLowPassFilter = new ComplexFIRFilter2(filterCoefficients, 1.0f); + + mBufferAssembler.setListener(new Listener() { @Override public void receive(ReusableComplexBuffer reusableComplexBuffer) { if(mReusableComplexBufferListener != null) { - ReusableComplexBuffer cleanupFiltered = mCleanupFilter.filter(reusableComplexBuffer); - ReusableComplexBuffer halfBandFiltered = mHalfBandFilter.filter(cleanupFiltered); - mReusableComplexBufferListener.receive(halfBandFiltered); + ReusableComplexBuffer filteredBuffer = mLowPassFilter.filter(reusableComplexBuffer); + mReusableComplexBufferListener.receive(filteredBuffer); +// mReusableComplexBufferListener.receive(reusableComplexBuffer); } else { @@ -386,20 +429,17 @@ public class ComplexPrimeCICDecimate implements Listener public void dispose() { - mAssembler.dispose(); - mCleanupFilter.dispose(); - mHalfBandFilter.dispose(); + mBufferAssembler.dispose(); + mLowPassFilter.dispose(); } /** - * Receiver method for the output adapter to receive a filtered, - * decimated sample, apply gain correction, apply cleanup filtering - * and output the sample values as a complex sample. + * Interface for receiving CIC decimated output samples */ @Override public void receive(float inphase, float quadrature) { - mAssembler.receive(new float[]{inphase, quadrature}); + mBufferAssembler.receive(inphase, quadrature); } public void setListener(Listener listener) @@ -411,5 +451,43 @@ public class ComplexPrimeCICDecimate implements Listener { mReusableComplexBufferListener = null; } + + /** + * 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, int passFrequency, int 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; + } } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/cic/ComplexPrimeCICDecimate2.java b/src/main/java/io/github/dsheirer/dsp/filter/cic/ComplexPrimeCICDecimate2.java deleted file mode 100644 index cc9bbab6..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/cic/ComplexPrimeCICDecimate2.java +++ /dev/null @@ -1,493 +0,0 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 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.cic; - -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.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler; -import io.github.dsheirer.sample.complex.ComplexSampleListener; -import io.github.dsheirer.sample.decimator.ComplexDecimator; -import org.apache.commons.lang3.Validate; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Non-Recursive Prime-Factor CIC Filter. - * - * Implements the CIC filter described in Understanding Digital Signal Processing, 3e, Lyons, on page 769. This - * filter is comprised of multiple decimating stages each with a prime factor decimation rate. Multiple stages are - * cascaded to achieve the overall decimation rate. - * - * This filter currently supports a maximum decimation rate of 2801. Higher decimation rates can be added by - * adding additional prime factors to the PRIMES array. - */ -public class ComplexPrimeCICDecimate2 implements Listener -{ -// private final static Logger mLog = LoggerFactory.getLogger(ComplexPrimeCICDecimate2.class); - - /** - * PRIME numbers to use for decimation stage sizing. This list contains values that should - * support input sample rates up to 70 MHz with an output channel rate of at least 25 kHz. - */ - public static int[] PRIMES = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, - 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, - 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, - 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, - 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, - 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, - 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, - 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, - 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, - 967, 971, 977, 983, 991, 997, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, - 1061, 1063, 1069, 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, - 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, - 1283, 1289, 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399, - 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, - 1493, 1499, 1511, 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, - 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, 1669, 1693, 1697, 1699, 1709, - 1721, 1723, 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, 1823, 1831, - 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951, - 1973, 1979, 1987, 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, - 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179, - 2203, 2207, 2213, 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, 2293, 2297, - 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, 2371, 2377, 2381, 2383, 2389, 2393, 2399, - 2411, 2417, 2423, 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, 2539, 2543, - 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, 2621, 2633, 2647, 2657, 2659, 2663, 2671, - 2677, 2683, 2687, 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753, - 2767, 2777, 2789, 2791, 2797, 2801 - }; - - private static Map sLowPassFilters = new HashMap(); - private static Map> sPrimeFactors = new HashMap(); - - private List mDecimatingStages = new ArrayList(); - private DecimatingStage mFirstDecimatingStage; - private Output mOutput; - - /** - * Constructs a new decimation filter. - * - * An example setup for a 2.4 MHz input sample rate and 25 kHz output channel rate with a desired 12.5 kHz channel - * pass band might be: - * sampleRate = 2,400,000.0 - * decimation = 96 - * passFrequency = 6250 (1/4 of the channel rate) - * stopFrequency = 7000 - * - * Note: the transition bandwidth (stop - start) directly impacts the size of the low-pass cleanup filter, - * where smaller transition bandwidth creates a longer filter that requires more processing effort. - * - * @param sampleRate of the input sample stream - * @param decimation - overall decimation rate - * @param passFrequency for the final cleanup filter - * @param stopFrequency for the final cleanup filter - * @throws FilterDesignException if a final low-pass cleanup filter cannot be created for the output channel rate - * and specified pass/stop frequencies. - */ - public ComplexPrimeCICDecimate2(double sampleRate, int decimation, int passFrequency, int stopFrequency) - throws FilterDesignException - { - Validate.isTrue(decimation <= PRIMES[PRIMES.length - 1]); - - List stageSizes = getPrimeFactors(decimation); - - for(int x = 0; x < stageSizes.size(); x++) - { - DecimatingStage stage = new DecimatingStage(stageSizes.get(x), 1); - - mDecimatingStages.add(stage); - - if(x == 0) - { - /* Reference to first stage -- will receive all samples */ - mFirstDecimatingStage = stage; - } - else - { - /* Wire the current stage to the previous stage */ - mDecimatingStages.get(x - 1).setListener(stage); - } - } - - double channelRate = sampleRate / (double)decimation; - - mOutput = new Output(channelRate, passFrequency, stopFrequency); - - mDecimatingStages.get(mDecimatingStages.size() - 1).setListener(mOutput); - } - - public void dispose() - { - for(DecimatingStage stage : mDecimatingStages) - { - stage.dispose(); - } - - mDecimatingStages.clear(); - mDecimatingStages = null; - mFirstDecimatingStage = null; - - mOutput.dispose(); - mOutput = null; - } - - /** - * Adds a listener to receive the output of this CIC decimation filter - */ - public void setListener(Listener listener) - { - mOutput.setListener(listener); - } - - /** - * Removes listener from output of this CIC decimation filter - */ - public void removeListener() - { - mOutput.removeListener(); - } - - /** - * Calculates the prime factors of the decimation rate. If you wish to have decimation rates higher than the - * highest rate listed in the PRIMES array, then add additional prime factors to the PRIMES array. - * - * @param decimation - integral decimation rate - * @return - ordered list (smallest to largest) of prime factors - */ - public static List getPrimeFactors(int decimation) - { - if(decimation > PRIMES[PRIMES.length - 1]) - { - throw new IllegalArgumentException("Decimation rates higher than [" + PRIMES[PRIMES.length - 1] + - "] are not supported. Update the PRIMES array to add support for higher decimation rates"); - } - - if(sPrimeFactors.containsKey(decimation)) - { - return sPrimeFactors.get(decimation); - } - - List primeFactors = new ArrayList(); - - int pointer = 0; - - while(decimation > 0 && pointer < PRIMES.length) - { - int prime = PRIMES[pointer]; - - if(decimation % prime == 0) - { - primeFactors.add(prime); - decimation /= prime; - } - else - { - pointer++; - } - } - - sPrimeFactors.put(decimation, primeFactors); - - return primeFactors; - } - - /** - * Primary input method for receiving sample arrays composed as I,Q,I,Q, etc. - */ - @Override - public void receive(ReusableComplexBuffer buffer) - { - if(mFirstDecimatingStage != null) - { - float[] samples = buffer.getSamples(); - - for(int x = 0; x < samples.length; x += 2) - { - mFirstDecimatingStage.receive(samples[x], samples[x + 1]); - } - } - - buffer.decrementUserCount(); - } - - /** - * Decimating stage combines multiple CIC stages with a decimator. The - * number of stages is indicated by the order value and the size indicates - * the decimation rate of this stage. - */ - public class DecimatingStage implements ComplexSampleListener - { - private List mStages = new ArrayList(); - private Stage mFirstStage; - private ComplexDecimator mDecimator; - - public DecimatingStage(int size, int order) - { - for(int x = 0; x < order; x++) - { - Stage stage; - - if(size == 2) - { - stage = new TwoStage(); - } - else - { - stage = new Stage(size); - } - - mStages.add(stage); - - if(x == 0) - { - mFirstStage = stage; - } - else - { - mStages.get(x - 1).setListener(stage); - } - } - - mDecimator = new ComplexDecimator(size); - mStages.get(mStages.size() - 1).setListener(mDecimator); - } - - public void dispose() - { - for(Stage stage : mStages) - { - stage.dispose(); - } - - mStages.clear(); - mDecimator.dispose(); - mDecimator = null; - mFirstStage = null; - mStages = null; - } - - @Override - public void receive(float i, float q) - { - mFirstStage.receive(i, q); - } - - public void setListener(ComplexSampleListener listener) - { - mDecimator.setListener(listener); - } - } - - /** - * Single non-decimating CIC stage component. Uses a circular buffer and a running average internally to implement - * the stage so that stage size has essentially no impact on the computational requirements of the stage - */ - public class Stage implements ComplexSampleListener - { - protected ComplexSampleListener mListener; - - private float[] mISamples; - private float[] mQSamples; - - protected float mISum; - protected float mQSum; - - private int mSamplePointer = 0; - private int mSize; - - protected float mGain; - - protected Stage() - { - } - - public Stage(int size) - { - mSize = size - 1; - - mISamples = new float[mSize]; - mQSamples = new float[mSize]; - - float baseGain = 1.0f / (float)size; - mGain = baseGain * (1.0f / (1.0f - baseGain)); - } - - public void dispose() - { - mListener = null; - } - - public void receive(float i, float q) - { - /* Subtract the oldest sample and add in the newest sample */ - mISum = mISum - mISamples[mSamplePointer] + i; - mQSum = mQSum - mQSamples[mSamplePointer] + q; - - /* Overwrite the oldest sample with the newest */ - mISamples[mSamplePointer] = i; - mQSamples[mSamplePointer] = q; - - mSamplePointer++; - - if(mSamplePointer >= mSize) - { - mSamplePointer = 0; - } - - if(mListener != null) - { - mListener.receive((mISum * mGain), (mQSum * mGain)); - } - } - - public void setListener(ComplexSampleListener listener) - { - mListener = listener; - } - } - - /** - * Size 2 stage that eliminates the unnecessary circular buffer management for a two-stage. - */ - public class TwoStage extends Stage - { - public TwoStage() - { - mGain = 0.5f; - } - - public void receive(float i, float q) - { - if(mListener != null) - { - mListener.receive(((mISum + i) * mGain), ((mQSum + q) * mGain)); - } - - mISum = i; - mQSum = q; - } - } - - - /** - * Output adapter - applies gain correction and cleanup filter and broadcast - * to registered listener. - */ - public class Output implements ComplexSampleListener - { - /* Decimated output buffers will contain 1024 complex samples */ - private ReusableComplexBufferAssembler mBufferAssembler; - private ComplexFIRFilter2 mLowPassFilter; - private Listener mReusableComplexBufferListener; - - public Output(double outputSampleRate, int passFrequency, int stopFrequency) throws FilterDesignException - { - mBufferAssembler = new ReusableComplexBufferAssembler(2400, outputSampleRate); - - //This may throw an exception if we can't design a filter for the sample rate and pass/stop frequencies - float[] filterCoefficients = getLowPassFilter(outputSampleRate, passFrequency, stopFrequency); - mLowPassFilter = new ComplexFIRFilter2(filterCoefficients, 1.0f); - - mBufferAssembler.setListener(new Listener() - { - @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) - { - if(mReusableComplexBufferListener != null) - { - ReusableComplexBuffer filteredBuffer = mLowPassFilter.filter(reusableComplexBuffer); - mReusableComplexBufferListener.receive(filteredBuffer); -// mReusableComplexBufferListener.receive(reusableComplexBuffer); - } - else - { - reusableComplexBuffer.decrementUserCount(); - } - } - }); - } - - public void dispose() - { - mBufferAssembler.dispose(); - mLowPassFilter.dispose(); - } - - /** - * Interface for receiving CIC decimated output samples - */ - @Override - public void receive(float inphase, float quadrature) - { - mBufferAssembler.receive(inphase, quadrature); - } - - public void setListener(Listener listener) - { - mReusableComplexBufferListener = listener; - } - - public void removeListener() - { - mReusableComplexBufferListener = null; - } - - /** - * 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, int passFrequency, int 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; - } - } -} diff --git a/src/main/java/io/github/dsheirer/source/tuner/Tuner.java b/src/main/java/io/github/dsheirer/source/tuner/Tuner.java index 67e8131e..cfe6eae3 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/Tuner.java +++ b/src/main/java/io/github/dsheirer/source/tuner/Tuner.java @@ -51,7 +51,7 @@ public abstract class Tuner implements ISourceEventProcessor //Register to receive frequency and sample rate change notifications mTunerController.addListener(this::process); -// mChannelSourceManager = new TunerChannelSourceManager(mTunerController); +// mChannelSourceManager = new PolyphaseChannelSourceManager(mTunerController); mChannelSourceManager = new HeterodyneChannelSourceManager(mTunerController); //Register to receive channel count change notifications diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/CICTunerChannelSource.java b/src/main/java/io/github/dsheirer/source/tuner/channel/CICTunerChannelSource.java index 0ea4fdf8..c84df3fb 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/channel/CICTunerChannelSource.java +++ b/src/main/java/io/github/dsheirer/source/tuner/channel/CICTunerChannelSource.java @@ -15,26 +15,28 @@ ******************************************************************************/ package io.github.dsheirer.source.tuner.channel; -import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; import io.github.dsheirer.dsp.filter.cic.ComplexPrimeCICDecimate; +import io.github.dsheirer.dsp.filter.design.FilterDesignException; 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.Listener; import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue; import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; import io.github.dsheirer.sample.complex.Complex; -import io.github.dsheirer.source.tuner.Tuner; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import io.github.dsheirer.source.SourceEvent; import java.util.ArrayList; import java.util.List; -public class CICTunerChannelSource +/** + * Complex sample source that provides a frequency-translated and decimated sample buffer stream. Uses a + * CIC decimation filter that requires the decimation rate to be an integer multiple. Sample buffer processing + * occurs on a scheduled runnable thread. + */ +public class CICTunerChannelSource extends TunerChannelSource implements Listener { - private final static Logger mLog = LoggerFactory.getLogger(CICTunerChannelSource.class); +// private final static Logger mLog = LoggerFactory.getLogger(CICTunerChannelSource.class); //Maximum number of filled buffers for the blocking queue private static final int BUFFER_MAX_CAPACITY = 300; @@ -42,8 +44,11 @@ public class CICTunerChannelSource //Threshold for resetting buffer overflow condition private static final int BUFFER_OVERFLOW_RESET_THRESHOLD = 100; - private static double CHANNEL_RATE = 48000.0; - private static int CHANNEL_PASS_FREQUENCY = 12000; + //Pass band cutoff frequency for the complex channel - target = 12.5 kHz channel + private static final int CHANNEL_PASS_FREQUENCY = 6000; + + //Stop band frequency for the complex channel + private static final int CHANNEL_STOP_FREQUENCY = 6700; private OverflowableReusableBufferTransferQueue mBuffer; private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("CICTunerChannelSource"); @@ -51,51 +56,66 @@ public class CICTunerChannelSource private ComplexPrimeCICDecimate mDecimationFilter; private Listener mListener; private List mSampleBuffers = new ArrayList<>(); - private long mTunerFrequency = 0; - private double mTunerSampleRate; + private double mChannelSampleRate; private long mChannelFrequencyCorrection = 0; + private long mTunerFrequency; /** - * Implements a heterodyne/decimate Digital Drop Channel (DDC) to decimate the IQ output from a tuner down to a - * fixed 48 kHz IQ channel rate. + * Constructs a frequency translating and CIC decimating channel source. * - * Note: this class can only be used once (started and stopped) and a new tuner channel source must be requested - * from the tuner once this object has been stopped. This is because channels are managed dynamically and center - * tuned frequency may have changed since this source was obtained and thus the tuner might no longer be able to - * source this channel once it has been stopped. - * - * @param tunerChannel specifying the center frequency for the DDC + * @param producerSourceEventListener to receive sample stream start/stop requests + * @param tunerChannel that details the desired channel frequency and bandwidth + * @param sampleRate of the incoming sample stream + * @param decimation to use in the CIC decimation filter. + * @throws FilterDesignException if a final cleanup filter cannot be designed using the remez filter + * designer and the filter parameters. */ - public CICTunerChannelSource(Tuner tuner, TunerChannel tunerChannel) + public CICTunerChannelSource(Listener producerSourceEventListener, TunerChannel tunerChannel, + double sampleRate, int decimation) throws FilterDesignException { + super(producerSourceEventListener, tunerChannel); + + mDecimationFilter = new ComplexPrimeCICDecimate(sampleRate, decimation, CHANNEL_PASS_FREQUENCY, CHANNEL_STOP_FREQUENCY); + mBuffer = new OverflowableReusableBufferTransferQueue<>(BUFFER_MAX_CAPACITY, BUFFER_OVERFLOW_RESET_THRESHOLD); -// mBuffer.setSourceOverflowListener(this); + //Setup the frequency mixer to the current source frequency + mChannelSampleRate = sampleRate / (double)decimation; + mTunerFrequency = tunerChannel.getFrequency(); + long frequencyOffset = mTunerFrequency - getTunerChannel().getFrequency(); - //Setup the frequency translator to the current source frequency - mTunerFrequency = tuner.getTunerController().getFrequency(); - long frequencyOffset = mTunerFrequency - tunerChannel.getFrequency(); - mFrequencyCorrectionMixer = new Oscillator(frequencyOffset, tuner.getTunerController().getSampleRate()); + mFrequencyCorrectionMixer = new LowPhaseNoiseOscillator(frequencyOffset, sampleRate); + } - //Setup the decimation filter chain - setSampleRate(tuner.getTunerController().getSampleRate()); + @Override + public void dispose() + { + // } /** - * Sets the center frequency for the incoming sample buffers + * Primary interface for receiving incoming complex sample buffers to be frequency translated and decimated. + */ + @Override + public void receive(ReusableComplexBuffer buffer) + { + mBuffer.offer(buffer); + } + + /** + * Sets/updates the center frequency for the sample streaming being sent from the producer. + * * @param frequency in hertz */ - protected void setFrequency(long frequency) + @Override + public void setFrequency(long frequency) { mTunerFrequency = frequency; - - //Reset frequency correction so that consumer components can adjust - setFrequencyCorrection(0); - updateMixerFrequencyOffset(); } /** * Current frequency correction being applied to the channel sample stream + * * @return correction in hertz */ public long getFrequencyCorrection() @@ -105,61 +125,14 @@ public class CICTunerChannelSource /** * Changes the frequency correction value and broadcasts the change to the registered downstream listener. + * * @param correction current frequency correction value. */ - protected void setFrequencyCorrection(long correction) + public void setFrequencyCorrection(long correction) { mChannelFrequencyCorrection = correction; - updateMixerFrequencyOffset(); - -// broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection)); - } - - /** - * Primary input method for receiving complex sample buffers from the wideband source (ie tuner) - * @param buffer to receive and eventually process - */ - public void receive(ReusableComplexBuffer buffer) - { - mBuffer.offer(buffer); - } - - public void setListener(Listener listener) - { - /* Save a pointer to the listener so that if we have to change the - * decimation filter, we can re-add the listener */ - mListener = listener; - - mDecimationFilter.setListener(listener); - } - - public void removeListener(Listener listener) - { - mDecimationFilter.removeListener(); - } - - /** - * Updates the sample rate to the requested value and notifies any downstream components of the change - * @param sampleRate to set - */ - protected void setSampleRate(double sampleRate) - { - if(mTunerSampleRate != sampleRate) - { - mFrequencyCorrectionMixer.setSampleRate(sampleRate); - - /* Get new decimation filter */ - mDecimationFilter = FilterFactory.getDecimationFilter((int)sampleRate, (int)CHANNEL_RATE, 1, - CHANNEL_PASS_FREQUENCY, 60, Window.WindowType.HAMMING); - - /* re-add the original output listener */ - mDecimationFilter.setListener(mListener); - - mTunerSampleRate = sampleRate; - -// broadcastConsumerSourceEvent(SourceEvent.channelSampleRateChange(getSampleRate())); - } + broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection)); } /** @@ -168,48 +141,99 @@ public class CICTunerChannelSource */ private void updateMixerFrequencyOffset() { -// long offset = mTunerFrequency - getTunerChannel().getFrequency() - mChannelFrequencyCorrection; -// mFrequencyCorrectionMixer.setFrequency(offset); + long offset = mTunerFrequency - getTunerChannel().getFrequency() - mChannelFrequencyCorrection; + mFrequencyCorrectionMixer.setFrequency(offset); } + /** + * Sets the sample rate of the incoming sample stream from the producer + * + * @param sampleRate in hertz + */ + @Override + protected void setSampleRate(double sampleRate) + { + //Not implemented. Sample rate changes are not permitted once sample stream starts + } + + /** + * Sets the frequency correction for the outbound sample stream to the consumer + * + * @param correction in hertz + */ + @Override + protected void setChannelFrequencyCorrection(long correction) + { + mChannelFrequencyCorrection = correction; + broadcastConsumerSourceEvent(SourceEvent.channelFrequencyCorrectionChange(correction)); + updateMixerFrequencyOffset(); + } + + /** + * Frequency correction value currently being applied to the outbound sample stream + */ + @Override + public long getChannelFrequencyCorrection() + { + return mChannelFrequencyCorrection; + } + + /** + * Sets the listener to receive the complex buffer sample output from this channel + * + * @param complexBufferListener to receive complex buffers + */ + @Override + public void setListener(Listener complexBufferListener) + { + mDecimationFilter.setListener(complexBufferListener); + } + + @Override + public void removeListener(Listener listener) + { + mDecimationFilter.removeListener(); + } + + + @Override public double getSampleRate() { - return CHANNEL_RATE; + return mChannelSampleRate; } + /** + * Primary processing method that is invoked on a recurring basis to process any queued complex buffers. + * + * Mixes the target frequency to baseband and then passes the buffer to the CIC decimation filter + */ protected void processSamples() { - mBuffer.drainTo(mSampleBuffers, 20); + mBuffer.drainTo(mSampleBuffers); - for(ReusableComplexBuffer buffer : mSampleBuffers) + for(ReusableComplexBuffer complexBuffer : mSampleBuffers) { - float[] samples = buffer.getSamples(); + float[] samples = complexBuffer.getSamples(); - ReusableComplexBuffer reusableComplexBuffer = mReusableComplexBufferQueue.getBuffer(samples.length); - float[] translated = reusableComplexBuffer.getSamples(); + ReusableComplexBuffer translatedComplexBuffer = mReusableComplexBufferQueue.getBuffer(samples.length); + float[] translatedSamples = translatedComplexBuffer.getSamples(); /* Perform frequency translation */ for(int x = 0; x < samples.length; x += 2) { mFrequencyCorrectionMixer.rotate(); - translated[x] = Complex.multiplyInphase( - samples[x], samples[x + 1], mFrequencyCorrectionMixer.inphase(), mFrequencyCorrectionMixer.quadrature()); + translatedSamples[x] = Complex.multiplyInphase(samples[x], samples[x + 1], + mFrequencyCorrectionMixer.inphase(), mFrequencyCorrectionMixer.quadrature()); - translated[x + 1] = Complex.multiplyQuadrature( - samples[x], samples[x + 1], mFrequencyCorrectionMixer.inphase(), mFrequencyCorrectionMixer.quadrature()); + translatedSamples[x + 1] = Complex.multiplyQuadrature(samples[x], samples[x + 1], + mFrequencyCorrectionMixer.inphase(), mFrequencyCorrectionMixer.quadrature()); } - final ComplexPrimeCICDecimate filter = mDecimationFilter; - - if(filter != null) - { - filter.receive(reusableComplexBuffer); - } - - buffer.decrementUserCount(); + mDecimationFilter.receive(translatedComplexBuffer); + complexBuffer.decrementUserCount(); } mSampleBuffers.clear(); } -} \ No newline at end of file +} diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/CICTunerChannelSource2.java b/src/main/java/io/github/dsheirer/source/tuner/channel/CICTunerChannelSource2.java deleted file mode 100644 index 4bb77429..00000000 --- a/src/main/java/io/github/dsheirer/source/tuner/channel/CICTunerChannelSource2.java +++ /dev/null @@ -1,239 +0,0 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 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.source.tuner.channel; - -import io.github.dsheirer.dsp.filter.cic.ComplexPrimeCICDecimate2; -import io.github.dsheirer.dsp.filter.design.FilterDesignException; -import io.github.dsheirer.dsp.mixer.IOscillator; -import io.github.dsheirer.dsp.mixer.LowPhaseNoiseOscillator; -import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; -import io.github.dsheirer.sample.complex.Complex; -import io.github.dsheirer.source.SourceEvent; - -import java.util.ArrayList; -import java.util.List; - -/** - * Complex sample source that provides a frequency-translated and decimated sample buffer stream. Uses a - * CIC decimation filter that requires the decimation rate to be an integer multiple. Sample buffer processing - * occurs on a scheduled runnable thread. - */ -public class CICTunerChannelSource2 extends TunerChannelSource implements Listener -{ -// private final static Logger mLog = LoggerFactory.getLogger(CICTunerChannelSource2.class); - - //Maximum number of filled buffers for the blocking queue - private static final int BUFFER_MAX_CAPACITY = 300; - - //Threshold for resetting buffer overflow condition - private static final int BUFFER_OVERFLOW_RESET_THRESHOLD = 100; - - //Pass band cutoff frequency for the complex channel - target = 12.5 kHz channel - private static final int CHANNEL_PASS_FREQUENCY = 6000; - - //Stop band frequency for the complex channel - private static final int CHANNEL_STOP_FREQUENCY = 6700; - - private OverflowableReusableBufferTransferQueue mBuffer; - private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("CICTunerChannelSource"); - private IOscillator mFrequencyCorrectionMixer; - private ComplexPrimeCICDecimate2 mDecimationFilter; - private Listener mListener; - private List mSampleBuffers = new ArrayList<>(); - private double mChannelSampleRate; - private long mChannelFrequencyCorrection = 0; - private long mTunerFrequency; - - /** - * Constructs a frequency translating and CIC decimating channel source. - * - * @param producerSourceEventListener to receive sample stream start/stop requests - * @param tunerChannel that details the desired channel frequency and bandwidth - * @param sampleRate of the incoming sample stream - * @param decimation to use in the CIC decimation filter. - * @throws FilterDesignException if a final cleanup filter cannot be designed using the remez filter - * designer and the filter parameters. - */ - public CICTunerChannelSource2(Listener producerSourceEventListener, TunerChannel tunerChannel, - double sampleRate, int decimation) throws FilterDesignException - { - super(producerSourceEventListener, tunerChannel); - - mDecimationFilter = new ComplexPrimeCICDecimate2(sampleRate, decimation, CHANNEL_PASS_FREQUENCY, CHANNEL_STOP_FREQUENCY); - - mBuffer = new OverflowableReusableBufferTransferQueue<>(BUFFER_MAX_CAPACITY, BUFFER_OVERFLOW_RESET_THRESHOLD); - //Setup the frequency mixer to the current source frequency - mChannelSampleRate = sampleRate / (double)decimation; - mTunerFrequency = tunerChannel.getFrequency(); - long frequencyOffset = mTunerFrequency - getTunerChannel().getFrequency(); - - mFrequencyCorrectionMixer = new LowPhaseNoiseOscillator(frequencyOffset, sampleRate); - } - - @Override - public void dispose() - { - // - } - - /** - * Primary interface for receiving incoming complex sample buffers to be frequency translated and decimated. - */ - @Override - public void receive(ReusableComplexBuffer buffer) - { - mBuffer.offer(buffer); - } - - /** - * Sets/updates the center frequency for the sample streaming being sent from the producer. - * - * @param frequency in hertz - */ - @Override - public void setFrequency(long frequency) - { - mTunerFrequency = frequency; - updateMixerFrequencyOffset(); - } - - /** - * Current frequency correction being applied to the channel sample stream - * - * @return correction in hertz - */ - public long getFrequencyCorrection() - { - return mChannelFrequencyCorrection; - } - - /** - * Changes the frequency correction value and broadcasts the change to the registered downstream listener. - * - * @param correction current frequency correction value. - */ - public void setFrequencyCorrection(long correction) - { - mChannelFrequencyCorrection = correction; - updateMixerFrequencyOffset(); - broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection)); - } - - /** - * Calculates the local mixer frequency offset from the tuned frequency, - * channel's requested frequency, and channel frequency correction. - */ - private void updateMixerFrequencyOffset() - { - long offset = mTunerFrequency - getTunerChannel().getFrequency() - mChannelFrequencyCorrection; - mFrequencyCorrectionMixer.setFrequency(offset); - } - - /** - * Sets the sample rate of the incoming sample stream from the producer - * - * @param sampleRate in hertz - */ - @Override - protected void setSampleRate(double sampleRate) - { - //Not implemented. Sample rate changes are not permitted once sample stream starts - } - - /** - * Sets the frequency correction for the outbound sample stream to the consumer - * - * @param correction in hertz - */ - @Override - protected void setChannelFrequencyCorrection(long correction) - { - mChannelFrequencyCorrection = correction; - broadcastConsumerSourceEvent(SourceEvent.channelFrequencyCorrectionChange(correction)); - updateMixerFrequencyOffset(); - } - - /** - * Frequency correction value currently being applied to the outbound sample stream - */ - @Override - public long getChannelFrequencyCorrection() - { - return mChannelFrequencyCorrection; - } - - /** - * Sets the listener to receive the complex buffer sample output from this channel - * - * @param complexBufferListener to receive complex buffers - */ - @Override - public void setListener(Listener complexBufferListener) - { - mDecimationFilter.setListener(complexBufferListener); - } - - @Override - public void removeListener(Listener listener) - { - mDecimationFilter.removeListener(); - } - - - @Override - public double getSampleRate() - { - return mChannelSampleRate; - } - - /** - * Primary processing method that is invoked on a recurring basis to process any queued complex buffers. - * - * Mixes the target frequency to baseband and then passes the buffer to the CIC decimation filter - */ - protected void processSamples() - { - mBuffer.drainTo(mSampleBuffers); - - for(ReusableComplexBuffer complexBuffer : mSampleBuffers) - { - float[] samples = complexBuffer.getSamples(); - - ReusableComplexBuffer translatedComplexBuffer = mReusableComplexBufferQueue.getBuffer(samples.length); - float[] translatedSamples = translatedComplexBuffer.getSamples(); - - /* Perform frequency translation */ - for(int x = 0; x < samples.length; x += 2) - { - mFrequencyCorrectionMixer.rotate(); - - translatedSamples[x] = Complex.multiplyInphase(samples[x], samples[x + 1], - mFrequencyCorrectionMixer.inphase(), mFrequencyCorrectionMixer.quadrature()); - - translatedSamples[x + 1] = Complex.multiplyQuadrature(samples[x], samples[x + 1], - mFrequencyCorrectionMixer.inphase(), mFrequencyCorrectionMixer.quadrature()); - } - - mDecimationFilter.receive(translatedComplexBuffer); - complexBuffer.decrementUserCount(); - } - - mSampleBuffers.clear(); - } -} diff --git a/src/main/java/io/github/dsheirer/source/tuner/manager/HeterodyneChannelSourceManager.java b/src/main/java/io/github/dsheirer/source/tuner/manager/HeterodyneChannelSourceManager.java index 8520146a..1a1ed9e6 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/manager/HeterodyneChannelSourceManager.java +++ b/src/main/java/io/github/dsheirer/source/tuner/manager/HeterodyneChannelSourceManager.java @@ -20,7 +20,7 @@ import io.github.dsheirer.sample.Listener; import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.tuner.TunerController; -import io.github.dsheirer.source.tuner.channel.CICTunerChannelSource2; +import io.github.dsheirer.source.tuner.channel.CICTunerChannelSource; import io.github.dsheirer.source.tuner.channel.TunerChannel; import io.github.dsheirer.source.tuner.channel.TunerChannelSource; import org.slf4j.Logger; @@ -40,7 +40,7 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager private final static int OBJECTIVE_CHANNEL_SAMPLE_RATE = 30000; - private List mChannelSources = new CopyOnWriteArrayList<>(); + private List mChannelSources = new CopyOnWriteArrayList<>(); private SortedSet mTunerChannels = new TreeSet<>(); private TunerController mTunerController; private ChannelSourceEventProcessor mChannelSourceEventProcessor = new ChannelSourceEventProcessor(); @@ -73,7 +73,7 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager try { //Attempt to create the channel source first, in case we get a filter design exception - CICTunerChannelSource2 tunerChannelSource = new CICTunerChannelSource2(mChannelSourceEventProcessor, + CICTunerChannelSource tunerChannelSource = new CICTunerChannelSource(mChannelSourceEventProcessor, tunerChannel, mTunerController.getSampleRate(), decimation); //Add to the list of channel sources so that it will receive the tuner frequency change @@ -156,7 +156,7 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager */ private void broadcastToChannels(SourceEvent sourceEvent) { - for(CICTunerChannelSource2 channelSource: mChannelSources) + for(CICTunerChannelSource channelSource: mChannelSources) { try { @@ -176,7 +176,7 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager */ private void updateTunerFrequency(long tunerFrequency) { - for(CICTunerChannelSource2 channelSource : mChannelSources) + for(CICTunerChannelSource channelSource : mChannelSources) { channelSource.setFrequency(tunerFrequency); } @@ -193,21 +193,21 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager switch(sourceEvent.getEvent()) { case REQUEST_START_SAMPLE_STREAM: - if(sourceEvent.getSource() instanceof CICTunerChannelSource2) + if(sourceEvent.getSource() instanceof CICTunerChannelSource) { - mTunerController.addBufferListener((CICTunerChannelSource2)sourceEvent.getSource()); + mTunerController.addBufferListener((CICTunerChannelSource)sourceEvent.getSource()); } break; case REQUEST_STOP_SAMPLE_STREAM: - if(sourceEvent.getSource() instanceof CICTunerChannelSource2) + if(sourceEvent.getSource() instanceof CICTunerChannelSource) { - mTunerController.removeBufferListener((CICTunerChannelSource2)sourceEvent.getSource()); + mTunerController.removeBufferListener((CICTunerChannelSource)sourceEvent.getSource()); } break; case REQUEST_SOURCE_DISPOSE: - if(sourceEvent.getSource() instanceof CICTunerChannelSource2) + if(sourceEvent.getSource() instanceof CICTunerChannelSource) { - CICTunerChannelSource2 channelSource = (CICTunerChannelSource2)sourceEvent.getSource(); + CICTunerChannelSource channelSource = (CICTunerChannelSource)sourceEvent.getSource(); mChannelSources.remove(channelSource); mTunerChannels.remove(channelSource.getTunerChannel()); channelSource.dispose(); diff --git a/src/main/java/io/github/dsheirer/source/tuner/manager/TunerChannelSourceManager.java b/src/main/java/io/github/dsheirer/source/tuner/manager/PolyphaseChannelSourceManager.java similarity index 98% rename from src/main/java/io/github/dsheirer/source/tuner/manager/TunerChannelSourceManager.java rename to src/main/java/io/github/dsheirer/source/tuner/manager/PolyphaseChannelSourceManager.java index 496f119f..6b879060 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/manager/TunerChannelSourceManager.java +++ b/src/main/java/io/github/dsheirer/source/tuner/manager/PolyphaseChannelSourceManager.java @@ -26,20 +26,20 @@ import org.slf4j.LoggerFactory; import java.util.SortedSet; -public class TunerChannelSourceManager extends ChannelSourceManager +public class PolyphaseChannelSourceManager extends ChannelSourceManager { - private final static Logger mLog = LoggerFactory.getLogger(TunerChannelSourceManager.class); + private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSourceManager.class); private PolyphaseChannelManager mPolyphaseChannelManager; private TunerController mTunerController; /** - * TunerChannelSourceManager is responsible for managing the tuner's center tuned frequency and providing access to + * PolyphaseChannelSourceManager is responsible for managing the tuner's center tuned frequency and providing access to * polyphase tuner channel sources (ie DDCs). This class is responsible for determining IF a requested channel can * be provided and then adjusting the center frequency and provisioning a DDC Polyphase Tuner channel source. * * @param tunerController with a center tuned frequency that will be managed by this instance */ - public TunerChannelSourceManager(TunerController tunerController) + public PolyphaseChannelSourceManager(TunerController tunerController) { mTunerController = tunerController;