diff --git a/src/main/java/io/github/dsheirer/channel/state/ChannelState.java b/src/main/java/io/github/dsheirer/channel/state/ChannelState.java
index 1d192bc6..a7896f01 100644
--- a/src/main/java/io/github/dsheirer/channel/state/ChannelState.java
+++ b/src/main/java/io/github/dsheirer/channel/state/ChannelState.java
@@ -148,6 +148,8 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
mTrafficChannelCallEvent = null;
mSquelchLocked = false;
+
+ setState(State.TEARDOWN);
}
public void dispose()
diff --git a/src/main/java/io/github/dsheirer/controller/channel/ChannelController.java b/src/main/java/io/github/dsheirer/controller/channel/ChannelController.java
index de1b7cde..06d1becb 100644
--- a/src/main/java/io/github/dsheirer/controller/channel/ChannelController.java
+++ b/src/main/java/io/github/dsheirer/controller/channel/ChannelController.java
@@ -1,3 +1,18 @@
+/*******************************************************************************
+ * 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.controller.channel;
import com.jidesoft.swing.JideSplitPane;
@@ -24,8 +39,7 @@ import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
-public class ChannelController extends JPanel
- implements ActionListener, ListSelectionListener
+public class ChannelController extends JPanel implements ActionListener, ListSelectionListener
{
private static final long serialVersionUID = 1L;
@@ -168,9 +182,13 @@ public class ChannelController extends JPanel
if( index >= 0 )
{
int translatedIndex = mChannelTable.convertRowIndexToView( index );
- mChannelTable.setRowSelectionInterval( translatedIndex, translatedIndex );
- mChannelTable.scrollRectToVisible(
- new Rectangle( mChannelTable.getCellRect( translatedIndex, 0, true ) ) );
+
+ if(translatedIndex >= 0)
+ {
+ mChannelTable.setRowSelectionInterval( translatedIndex, translatedIndex );
+ mChannelTable.scrollRectToVisible(
+ new Rectangle( mChannelTable.getCellRect( translatedIndex, 0, true ) ) );
+ }
}
}
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/Filters.java b/src/main/java/io/github/dsheirer/dsp/filter/Filters.java
index 1d036505..fe4cd96a 100644
--- a/src/main/java/io/github/dsheirer/dsp/filter/Filters.java
+++ b/src/main/java/io/github/dsheirer/dsp/filter/Filters.java
@@ -1719,7 +1719,32 @@ public enum Filters
0.011873357051047719f, 0.0f, -0.007901319195893647f, 0.0f,
0.005055504379767936f, 0.0f, -0.003054430179754289f, 0.0f,
0.001695637278417295f, 0.0f, -0.000998606272947510f
- } );
+ }),
+
+ /**
+ * FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
+ * .sampleRate(100)
+ * .gridDensity(16)
+ * .oddLength(true)
+ * .passBandCutoff(21)
+ * .passBandAmplitude(1.0)
+ * .passBandRipple(0.01)
+ * .stopBandStart(29)
+ * .stopBandAmplitude(0.0)
+ * .stopBandRipple(0.01) //Approximately -85 dB suppression
+ * .build();
+ */
+ HALF_BAND_FILTER_27T(new float[]
+ {
+ 0.007495115f, 0.0f, -0.011045938f, 0.0f,
+ 0.019129561f, 0.0f, -0.031747308f, 0.0f,
+ 0.053220540f, 0.0f, -0.099569574f, 0.0f,
+ 0.316083550f, 0.5f, 0.316083550f, 0.0f,
+ -0.099569574f, 0.0f, 0.053220540f, 0.0f,
+ -0.031747308f, 0.0f, 0.019129561f, 0.0f,
+ -0.011045938f, 0.0f, 0.007495115f
+
+ });
float[] mCoefficients;
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..86ad51ab 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
@@ -34,7 +34,6 @@ import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -172,36 +171,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
return channelSource;
}
- public TunerChannelSource getTestChannel(int index)
- {
- PolyphaseChannelSource channelSource = null;
-
- try
- {
- List polyphaseIndexes = new ArrayList<>();
- polyphaseIndexes.add(index);
-
- IPolyphaseChannelOutputProcessor outputProcessor = getOutputProcessor(polyphaseIndexes);
-
-
- if(outputProcessor != null)
- {
- long centerFrequency = mChannelCalculator.getCenterFrequencyForIndexes(polyphaseIndexes);
-
- TunerChannel tunerChannel = new TunerChannel(centerFrequency, 12500);
-
- channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
- mChannelCalculator.getChannelSampleRate(), centerFrequency);
- }
- }
- catch(IllegalArgumentException iae)
- {
- mLog.error("Couldn't get test channel with index:" + index, iae);
- }
-
- return channelSource;
- }
-
/**
* Creates a processor to process the channelizer channel indexes into a composite output stream providing
* channelized complex sample buffers to a registered source listener.
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 f8689efb..7b33b9aa 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
@@ -201,7 +201,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
* @param frequency in hertz
*/
@Override
- protected void setFrequency(long frequency)
+ public void setFrequency(long frequency)
{
mIndexCenterFrequency = frequency;
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
new file mode 100644
index 00000000..cc9bbab6
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/filter/cic/ComplexPrimeCICDecimate2.java
@@ -0,0 +1,493 @@
+/*******************************************************************************
+ * 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/dsp/filter/design/FilterViewer.java b/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java
index 9cf838eb..c6bbb29f 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
@@ -8,6 +8,8 @@ import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.Arrays;
+
public class FilterViewer extends Application
{
private final static Logger mLog = LoggerFactory.getLogger(FilterViewer.class);
@@ -39,16 +41,27 @@ public class FilterViewer extends Application
*/
private float[] getFilter()
{
+ FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
+ .sampleRate(25000)
+ .gridDensity(16)
+ .passBandCutoff(6000)
+ .passBandAmplitude(1.0)
+ .passBandRipple(0.01)
+ .stopBandStart(6700)
+ .stopBandAmplitude(0.0)
+ .stopBandRipple(0.03)
+ .build();
+
// FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
-// .sampleRate(8000)
+// .sampleRate(100)
// .gridDensity(16)
// .oddLength(true)
-// .passBandCutoff(300)
+// .passBandCutoff(21)
// .passBandAmplitude(1.0)
// .passBandRipple(0.01)
-// .stopBandStart(500)
+// .stopBandStart(29)
// .stopBandAmplitude(0.0)
-// .stopBandRipple(0.03) //Approximately 60 dB attenuation
+// .stopBandRipple(0.01)
// .build();
//
// FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder()
@@ -61,15 +74,15 @@ public class FilterViewer extends Application
// .passBandRipple(0.01)
// .build();
- FIRFilterSpecification specification = FIRFilterSpecification.bandPassBuilder()
- .sampleRate(8000)
- .stopFrequency1(1000)
- .passFrequencyBegin(1100)
- .passFrequencyEnd(1900)
- .stopFrequency2(2000)
- .stopRipple(0.000001)
- .passRipple(0.00001)
- .build();
+// FIRFilterSpecification specification = FIRFilterSpecification.bandPassBuilder()
+// .sampleRate(8000)
+// .stopFrequency1(1000)
+// .passFrequencyBegin(1100)
+// .passFrequencyEnd(1900)
+// .stopFrequency2(2000)
+// .stopRipple(0.000001)
+// .passRipple(0.00001)
+// .build();
float[] taps = null;
@@ -102,7 +115,19 @@ public class FilterViewer extends Application
throw new IllegalStateException("Couldn't design filter");
}
+// try
+// {
+// taps = FilterFactory.getSincM2Synthesizer(0.25, 2, 9);
+// taps = FilterFactory.getSincM2Channelizer(12500.0, 2, 9, true);
+// taps = FilterFactory.getSincM2Synthesizer( 25000.0, 12500.0, 2, 9);
+//
+// }
+// catch(FilterDesignException e)
+// {
+// e.printStackTrace();
+// }
+ mLog.debug(Arrays.toString(taps));
return taps;
}
}
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/complex/ComplexHalfBandFilter2.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/complex/ComplexHalfBandFilter2.java
new file mode 100644
index 00000000..6ab51f04
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/complex/ComplexHalfBandFilter2.java
@@ -0,0 +1,108 @@
+/*******************************************************************************
+ * 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.halfband.complex;
+
+import io.github.dsheirer.dsp.filter.Filters;
+import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter2;
+import io.github.dsheirer.dsp.mixer.Oscillator;
+import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
+import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
+
+public class ComplexHalfBandFilter2
+{
+ private ReusableComplexBufferQueue mBufferQueue = new ReusableComplexBufferQueue("ComplexHalfBandFilter2");
+
+ private HalfBandFilter2 mIFilter;
+ private HalfBandFilter2 mQFilter;
+ private float mResidualISample;
+ private float mResidualQSample;
+ private boolean mHasResidual;
+ private int mOutputBufferLength;
+ private float[] mSamples;
+ private int mSamplesPointer;
+ private float[] mFilteredSamples;
+ private int mFilteredSamplesPointer;
+
+ public ComplexHalfBandFilter2(float[] coefficients, float gain)
+ {
+ mIFilter = new HalfBandFilter2(coefficients, gain);
+ mQFilter = new HalfBandFilter2(coefficients, gain);
+ }
+
+ public ReusableComplexBuffer filter(ReusableComplexBuffer originalBuffer)
+ {
+ mSamples = originalBuffer.getSamples();
+ mSamplesPointer = 0;
+
+ mOutputBufferLength = (mHasResidual ? (mSamples.length + 2) : mSamples.length) / 4 * 2;
+
+ ReusableComplexBuffer filteredBuffer = mBufferQueue.getBuffer(mOutputBufferLength);
+ mFilteredSamples = filteredBuffer.getSamples();
+ mFilteredSamplesPointer = 0;
+
+ if(mHasResidual)
+ {
+ mFilteredSamples[mFilteredSamplesPointer++] = mIFilter.filter(mResidualISample, mSamples[mSamplesPointer++]);
+ mFilteredSamples[mFilteredSamplesPointer++] = mQFilter.filter(mResidualQSample, mSamples[mSamplesPointer++]);
+ mHasResidual = false;
+ }
+
+ while(mSamplesPointer + 3 < mSamples.length)
+ {
+ mFilteredSamples[mFilteredSamplesPointer++] = mIFilter.filter(mSamples[mSamplesPointer], mSamples[mSamplesPointer + 2]);
+ mFilteredSamples[mFilteredSamplesPointer++] = mIFilter.filter(mSamples[mSamplesPointer + 1], mSamples[mSamplesPointer + 3]);
+ mSamplesPointer += 4;
+ }
+
+ if(mSamplesPointer < mSamples.length)
+ {
+ mHasResidual = true;
+ mResidualISample = mSamples[mSamplesPointer++];
+ mResidualQSample = mSamples[mSamplesPointer];
+ }
+
+ originalBuffer.decrementUserCount();
+ return filteredBuffer;
+ }
+
+ public static void main(String[] args)
+ {
+ Oscillator oscillator = new Oscillator(1, 16);
+ ComplexHalfBandFilter2 filter = new ComplexHalfBandFilter2(Filters.HALF_BAND_FILTER_27T.getCoefficients(), 1.0f);
+
+ ReusableComplexBufferQueue bufferQueue = new ReusableComplexBufferQueue("Test");
+
+ ReusableComplexBuffer complexBuffer = bufferQueue.getBuffer(8);
+ oscillator.generateComplex(complexBuffer);
+ ReusableComplexBuffer filteredBuffer = filter.filter(complexBuffer);
+ filteredBuffer.decrementUserCount();
+
+ ReusableComplexBuffer complexBuffer2 = bufferQueue.getBuffer(10);
+ oscillator.generateComplex(complexBuffer2);
+ ReusableComplexBuffer filteredBuffer2 = filter.filter(complexBuffer2);
+ filteredBuffer2.decrementUserCount();
+
+ ReusableComplexBuffer complexBuffer3 = bufferQueue.getBuffer(8);
+ oscillator.generateComplex(complexBuffer3);
+ ReusableComplexBuffer filteredBuffer3 = filter.filter(complexBuffer3);
+ filteredBuffer3.decrementUserCount();
+
+
+
+
+
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/real/HalfBandFilter2.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/real/HalfBandFilter2.java
new file mode 100644
index 00000000..4b4ccbde
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/real/HalfBandFilter2.java
@@ -0,0 +1,89 @@
+/*******************************************************************************
+ * 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.halfband.real;
+
+/**
+ * Implements a half-band filter that produces one filtered output for every two input samples.
+ *
+ * This filter is structured for Java 8+ compiler JIT optimization for SIMD instructions when
+ * supported by the host CPU.
+ */
+public class HalfBandFilter2
+{
+ private static final float CENTER_COEFFICIENT = 0.5f;
+ private float[] mCoefficients;
+ private float[] mEvenSamples;
+ private float[] mOddSamples;
+ private int mCenterSampleIndex;
+ private float mAccumulator;
+ private int mPointer;
+ private float mGain;
+
+ /**
+ * Creates a half band filter with inherent decimation by two.
+ *
+ * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
+ * zero valued except for the middle odd index coefficient which should be valued 0.5
+ * @param gain value to apply to the output
+ */
+ public HalfBandFilter2(float[] coefficients, float gain)
+ {
+ if(coefficients.length % 2 == 0)
+ {
+ throw new IllegalArgumentException("Half-band filter coefficients must be odd-length");
+ }
+
+ mGain = gain;
+
+ int half = coefficients.length / 2 + 1;
+
+ mCoefficients = new float[half];
+ mEvenSamples = new float[half];
+
+ //Use only the even coefficients since the odd coefficients are all zero-valued
+ for(int x = 0; x < coefficients.length; x += 2)
+ {
+ mCoefficients[x / 2] = coefficients[x];
+ }
+
+ //Setup the odd samples array as a simple delay line, half the size of the even samples array
+ mOddSamples = new float[half / 2];
+ mCenterSampleIndex = mOddSamples.length - 1;
+ }
+
+ public float filter(float sample1, float sample2)
+ {
+ //Structured for SIMD array copy optimization when supported by host CPU
+ System.arraycopy(mEvenSamples, 0, mEvenSamples, 1, mEvenSamples.length - 1);
+ System.arraycopy(mOddSamples, 0, mOddSamples, 1, mOddSamples.length - 1);
+
+ mEvenSamples[0] = sample1;
+ mOddSamples[0] = sample2;
+
+ mAccumulator = 0.0f;
+
+ //Structured for SIMD dot.product optimization when supported by host CPU
+ for(mPointer = 0; mPointer < mEvenSamples.length; mPointer++)
+ {
+ mAccumulator += (mEvenSamples[mPointer] * mCoefficients[mPointer]);
+ }
+
+ mAccumulator += (mOddSamples[mCenterSampleIndex] * CENTER_COEFFICIENT);
+ mAccumulator *= mGain;
+
+ return mAccumulator;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/AbstractOscillator.java b/src/main/java/io/github/dsheirer/dsp/mixer/AbstractOscillator.java
index 144ff1a4..30594c41 100644
--- a/src/main/java/io/github/dsheirer/dsp/mixer/AbstractOscillator.java
+++ b/src/main/java/io/github/dsheirer/dsp/mixer/AbstractOscillator.java
@@ -127,8 +127,8 @@ public abstract class AbstractOscillator implements IOscillator
for(int x = 0; x < sampleCount; x++)
{
- samples[x] = inphase();
rotate();
+ samples[x] = quadrature();
}
return samples;
diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/LowPhaseNoiseOscillator.java b/src/main/java/io/github/dsheirer/dsp/mixer/LowPhaseNoiseOscillator.java
index 09c772d0..7ae6e9dc 100644
--- a/src/main/java/io/github/dsheirer/dsp/mixer/LowPhaseNoiseOscillator.java
+++ b/src/main/java/io/github/dsheirer/dsp/mixer/LowPhaseNoiseOscillator.java
@@ -18,6 +18,10 @@ package io.github.dsheirer.dsp.mixer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
public class LowPhaseNoiseOscillator extends AbstractOscillator
{
private final static Logger mLog = LoggerFactory.getLogger(LowPhaseNoiseOscillator.class);
@@ -82,4 +86,54 @@ public class LowPhaseNoiseOscillator extends AbstractOscillator
//Update the gain value for the next rotation
mGain = THREE_HALVES - ((mPreviousInphase * mPreviousInphase) + (mPreviousQuadrature * mPreviousQuadrature));
}
+
+ public static long process(AbstractOscillator oscillator, int iterations, int sampleCount)
+ {
+ long start = System.nanoTime();
+
+ for(int x = 0; x < iterations; x++)
+ {
+ float[] samples = oscillator.generateComplex(sampleCount);
+ }
+
+ long duration = System.nanoTime() - start;
+
+ return duration;
+ }
+
+ public static void main(String[] args)
+ {
+ mLog.debug("Starting ...");
+ SimpleDateFormat sdf = new SimpleDateFormat("mm:ss.SSS");
+
+ AbstractOscillator lpno = new LowPhaseNoiseOscillator(100, 800);
+ AbstractOscillator regular = new Oscillator(100, 800);
+
+ int iterations = 10000;
+ int sampleCount = 1000000;
+
+ mLog.debug("Warm up - lpno");
+ process(lpno, iterations, sampleCount);
+ process(lpno, iterations, sampleCount);
+ process(lpno, iterations, sampleCount);
+
+ mLog.debug("Warm up - regular");
+ process(regular, iterations, sampleCount);
+ process(regular, iterations, sampleCount);
+ process(regular, iterations, sampleCount);
+
+ mLog.debug("Test - lpno");
+ process(lpno, iterations, sampleCount);
+ process(lpno, iterations, sampleCount);
+ long duration = process(lpno, iterations, sampleCount);
+ mLog.debug("Duration Low Phase Noise: " + sdf.format(new Date(TimeUnit.NANOSECONDS.toMillis(duration))));
+
+ mLog.debug("Test - regular");
+ process(regular, iterations, sampleCount);
+ process(regular, iterations, sampleCount);
+ duration = process(regular, iterations, sampleCount);
+ mLog.debug("Duration Regular: " + sdf.format(new Date(TimeUnit.NANOSECONDS.toMillis(duration))));
+
+// mLog.debug(" Delta: " + sdf.format(new Date(TimeUnit.NANOSECONDS.toMillis(duration2 - duration))));
+ }
}
diff --git a/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer.java b/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer.java
index 8565848b..8f9df9f9 100644
--- a/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer.java
+++ b/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer.java
@@ -259,7 +259,8 @@ public class ChannelizerViewer extends JFrame
for(int x = 0; x < mChannelCount; x++)
{
- TunerChannelSource source = mTestTuner.getChannelSourceManager().getTestingSource(x);
+ TunerChannel tunerChannel = new TunerChannel(100000000, 12500);
+ TunerChannelSource source = mTestTuner.getChannelSourceManager().getSource(tunerChannel);
DiscreteChannelPanel channelPanel = new DiscreteChannelPanel(mSettingsManager, source, x);
channelPanel.setDFTSize(mChannelPanelDFTSize);
diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSM.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSM.java
index be99d126..7a2d3116 100644
--- a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSM.java
+++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSM.java
@@ -26,6 +26,8 @@ import io.github.dsheirer.dsp.psk.pll.AdaptivePLLGainMonitor;
import io.github.dsheirer.dsp.psk.pll.CostasLoop;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.source.SourceEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
@@ -34,6 +36,8 @@ public class P25DecoderLSM extends P25Decoder
{
protected static final float SAMPLE_COUNTER_GAIN = 0.3f;
+ private final static Logger mLog = LoggerFactory.getLogger(P25DecoderLSM.class);
+
private Map mBasebandFilters = new HashMap<>();
private ComplexFIRFilter2 mBasebandFilter;
private ComplexFeedForwardGainControl mAGC = new ComplexFeedForwardGainControl(32);
@@ -135,6 +139,7 @@ public class P25DecoderLSM extends P25Decoder
break;
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
//Reset the PLL if/when the tuner PPM changes so that we can re-lock
+ mLog.debug("******** Resetting costas loop for frequency correction change");
mCostasLoop.reset();
break;
}
diff --git a/src/main/java/io/github/dsheirer/record/wave/WaveWriter.java b/src/main/java/io/github/dsheirer/record/wave/WaveWriter.java
index 9a7dd5f3..d6901da9 100644
--- a/src/main/java/io/github/dsheirer/record/wave/WaveWriter.java
+++ b/src/main/java/io/github/dsheirer/record/wave/WaveWriter.java
@@ -151,7 +151,7 @@ public class WaveWriter implements AutoCloseable
int suffix = 1;
- while(Files.exists(renamedPath))
+ while(Files.exists(renamedPath) && suffix < 4)
{
String renamedWithSuffix = mFile.getFileName().toString();
renamedWithSuffix = renamedWithSuffix.replace(".tmp", "_" + suffix + ".wav");
diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBufferAssembler.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBufferAssembler.java
index 8a06094a..fc4a8c99 100644
--- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBufferAssembler.java
+++ b/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBufferAssembler.java
@@ -63,6 +63,29 @@ public class ReusableComplexBufferAssembler
mListener = null;
}
+ /**
+ * Adds the samples to this assembler. As each buffer is assembled, it will be dispatched to the registered
+ * listener.
+ *
+ * @param inphase sample to assemble
+ * @param quadrature sample to assemble
+ */
+ public void receive(float inphase, float quadrature)
+ {
+ if(mBuffer.remaining() >= 2)
+ {
+ mBuffer.put(inphase);
+ mBuffer.put(quadrature);
+
+ mTimestampManager.increment(1);
+
+ if(!mBuffer.hasRemaining())
+ {
+ flush();
+ }
+ }
+ }
+
/**
* Adds the samples to this assembler. As each buffer is assembled, it will be dispatched to the registered
* listener.
diff --git a/src/main/java/io/github/dsheirer/sample/decimator/ComplexDecimator.java b/src/main/java/io/github/dsheirer/sample/decimator/ComplexDecimator.java
index 180ed7c7..b9445ddf 100644
--- a/src/main/java/io/github/dsheirer/sample/decimator/ComplexDecimator.java
+++ b/src/main/java/io/github/dsheirer/sample/decimator/ComplexDecimator.java
@@ -1,58 +1,75 @@
+/*******************************************************************************
+ * 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.sample.decimator;
import io.github.dsheirer.sample.complex.ComplexSampleListener;
-
+/**
+ * Decimator for complex samples
+ */
public class ComplexDecimator implements ComplexSampleListener
{
- private ComplexSampleListener mListener;
- private int mCounter = 0;
- private int mDecimationRate;
+ private ComplexSampleListener mListener;
+ private int mCounter = 0;
+ private int mDecimationRate;
- /**
- * Constructs a new Decimator object with the specified decimation rate.
- */
- public ComplexDecimator( int rate )
- {
- mDecimationRate = rate;
- }
-
- public void dispose()
- {
- mListener = null;
- }
-
- /**
- * Sets a new decimation rate. Rate can be changed after this object
- * is constructed.
- */
- public synchronized void setDecimationRate( int rate )
- {
- mDecimationRate = rate;
- }
-
- /**
- * Receives samples allowing only 1 of every (rate) sample to go on
- * to the registered listener
- */
- @Override
- public void receive( float i, float q )
+ /**
+ * Constructs a new Decimator object with the specified decimation rate.
+ */
+ public ComplexDecimator(int rate)
{
- mCounter++;
-
- if( mCounter >= mDecimationRate )
- {
- mListener.receive( i, q );
-
- mCounter = 0;
- }
+ mDecimationRate = rate;
+ }
+
+ public void dispose()
+ {
+ mListener = null;
+ }
+
+ /**
+ * Sets a new decimation rate. Rate can be changed after this object
+ * is constructed.
+ */
+ public synchronized void setDecimationRate(int rate)
+ {
+ mDecimationRate = rate;
+ }
+
+ /**
+ * Receives samples allowing only 1 of every (rate) sample to go on
+ * to the registered listener
+ */
+ @Override
+ public void receive(float i, float q)
+ {
+ mCounter++;
+
+ if(mCounter >= mDecimationRate)
+ {
+ mListener.receive(i, q);
+
+ mCounter = 0;
+ }
+ }
+
+ /**
+ * Sets the decimated output listener
+ */
+ public void setListener(ComplexSampleListener listener)
+ {
+ mListener = listener;
}
-
- /**
- * Sets the decimated output listener
- */
- public void setListener( ComplexSampleListener listener )
- {
- mListener = listener;
- }
}
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 857db597..67e8131e 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/Tuner.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/Tuner.java
@@ -21,7 +21,7 @@ import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.TunerEvent.Event;
import io.github.dsheirer.source.tuner.manager.ChannelSourceManager;
-import io.github.dsheirer.source.tuner.manager.TunerChannelSourceManager;
+import io.github.dsheirer.source.tuner.manager.HeterodyneChannelSourceManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -51,7 +51,9 @@ 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 TunerChannelSourceManager(mTunerController);
+ mChannelSourceManager = new HeterodyneChannelSourceManager(mTunerController);
+
//Register to receive channel count change notifications
mChannelSourceManager.addSourceEventListener(this::process);
diff --git a/src/main/java/io/github/dsheirer/source/tuner/TunerModel.java b/src/main/java/io/github/dsheirer/source/tuner/TunerModel.java
index 3350bcae..3b904a83 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/TunerModel.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/TunerModel.java
@@ -289,15 +289,7 @@ public class TunerModel extends AbstractTableModel implements Listener 0)
- {
- return channelCount + " (LOCKED)";
- }
- else
- {
- return "0";
- }
+ return channelCount + " (" + (tuner.getTunerController().isLocked() ? "LOCKED)" : "UNLOCKED)");
case FREQUENCY_ERROR:
double ppm = tuner.getTunerController().getFrequencyCorrection();
return mFrequencyErrorPPMFormat.format(ppm);
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
new file mode 100644
index 00000000..4bb77429
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/source/tuner/channel/CICTunerChannelSource2.java
@@ -0,0 +1,239 @@
+/*******************************************************************************
+ * 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/channel/TunerChannelSource.java b/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java
index 8d9f9482..c86ddf51 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java
@@ -66,7 +66,7 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
* Sets the center frequency for the sample streaming being sent from the producer.
* @param frequency in hertz
*/
- protected abstract void setFrequency(long frequency);
+ public abstract void setFrequency(long frequency);
/**
* Sets the sample rate of the incoming sample stream from the producer
@@ -118,12 +118,7 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
{
//Broadcast current frequency and sample rate so consumer can configure correctly
broadcastConsumerSourceEvent(SourceEvent.frequencyChange(getFrequency()));
-
- if(mProducerSourceEventListener != null)
- {
- mProducerSourceEventListener.receive(SourceEvent.startSampleStreamRequest(this));
- }
-
+ broadcastProducerSourceEvent(SourceEvent.startSampleStreamRequest(this));
mScheduledIntervalProcessor.start();
}
@@ -133,12 +128,8 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
*/
public void stop()
{
- if(mProducerSourceEventListener != null)
- {
- mProducerSourceEventListener.receive(SourceEvent.stopSampleStreamRequest(this));
- mProducerSourceEventListener.receive(SourceEvent.sourceDisposeRequest(this));
- }
-
+ broadcastProducerSourceEvent(SourceEvent.stopSampleStreamRequest(this));
+ broadcastProducerSourceEvent(SourceEvent.sourceDisposeRequest(this));
mScheduledIntervalProcessor.stop();
}
@@ -171,6 +162,7 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
{
switch(sourceEvent.getEvent())
{
+ case NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
case NOTIFICATION_FREQUENCY_CHANGE:
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_LOCKED:
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_UNLOCKED:
diff --git a/src/main/java/io/github/dsheirer/source/tuner/manager/CenterFrequencyCalculator.java b/src/main/java/io/github/dsheirer/source/tuner/manager/CenterFrequencyCalculator.java
new file mode 100644
index 00000000..c61b8ba2
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/source/tuner/manager/CenterFrequencyCalculator.java
@@ -0,0 +1,150 @@
+/*******************************************************************************
+ * 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.manager;
+
+import io.github.dsheirer.source.tuner.TunerController;
+import io.github.dsheirer.source.tuner.channel.TunerChannel;
+
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * Calculates tuner center frequency for a set of tuner channels.
+ */
+public class CenterFrequencyCalculator
+{
+ public static final long INVALID_FREQUENCY = -1;
+
+ /**
+ * Determines the optimal center frequency for a given tuner and set of tuner channels.
+ *
+ * @param tunerController that is providing tuner channels
+ * @param channels to calculate
+ * @return optimal center frequency or INVALID_FREQUENCY if a center frequency cannot be calculated.
+ */
+ public static long getCenterFrequency(TunerController tunerController, SortedSet channels)
+ {
+ long candidateFrequency = INVALID_FREQUENCY;
+
+ boolean isValidCandidateFrequency = true;
+
+ //If there is only 1 channel set the center frequency so that the channel is positioned to the right of center
+ if(channels.size() == 1)
+ {
+ candidateFrequency = channels.first().getMinFrequency() - tunerController.getMiddleUnusableHalfBandwidth() + 1;
+ }
+ else
+ {
+ long minChannelFrequency = channels.first().getMinFrequency();
+ long maxChannelFrequency = channels.last().getMaxFrequency();
+
+ //Start by placing the highest frequency channel at the high end of the spectrum
+ candidateFrequency = maxChannelFrequency - (tunerController.getUsableBandwidth() / 2);
+
+ //Iterate the channels and make sure that none of them overlap the center DC spike buffer, if one exists
+ if(tunerController.getMiddleUnusableHalfBandwidth() > 0)
+ {
+ boolean processingRequired = true;
+
+ while(isValidCandidateFrequency && processingRequired)
+ {
+ processingRequired = false;
+
+ long minAvoid = candidateFrequency - tunerController.getMiddleUnusableHalfBandwidth();
+ long maxAvoid = candidateFrequency + tunerController.getMiddleUnusableHalfBandwidth();
+
+ //If any of the center channel(s) overlap the central DC spike avoid area, we'll iteratively
+ //increase the tuned frequency causing the set of channels to move left in the tuned bandwidth until
+ //we either find a good center tune frequency, or we walk the lowest frequency channel out of the
+ //minimum tuned range, in which case we'll throw an exception indicating we don't have a solution.
+ for(TunerChannel channel : channels)
+ {
+ if(channel.overlaps(minAvoid, maxAvoid))
+ {
+ //Calculate a tuned frequency adjustment that places this overlapping channel just to the
+ //left of the central DC spike avoid zone
+ long adjustment = channel.getMaxFrequency() - minAvoid + 1;
+
+ //If the candidate frequency doesn't push the lowest channel out of bounds, make adjustment
+ if(candidateFrequency + adjustment - (tunerController.getUsableBandwidth() / 2) <= minChannelFrequency)
+ {
+ candidateFrequency += adjustment;
+ processingRequired = true;
+ }
+ //Otherwise, punt and indicate that we can't find a center frequency or add the channel
+ else
+ {
+ isValidCandidateFrequency = false;
+ }
+
+ //break out of the for/each loop, so that we can start over again with all of the channels
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if(isValidCandidateFrequency)
+ {
+ return candidateFrequency;
+ }
+
+ return INVALID_FREQUENCY;
+ }
+
+ /**
+ * Indicates if channel along with all of the other currently sourced
+ * channels can fit within the tunable bandwidth.
+ *
+ */
+ /**
+ *
+ * Indicates if channel along with all of the other currently sourced
+ * channels can fit within the tunable bandwidth.
+
+ * @param channel to test
+ * @param tunerController that will provide the channel
+ * @param channels is a current set of channels being sourced by the tuner controller
+ * @return
+ */
+ public static boolean canTune(TunerChannel channel, TunerController tunerController, SortedSet channels)
+ {
+ //Make sure we're within the tunable frequency range of this tuner
+ if(tunerController.getMinFrequency() < channel.getMinFrequency() && tunerController.getMaxFrequency() > channel.getMaxFrequency())
+ {
+ //If this is the first lock, then we're good
+ if(channels.isEmpty())
+ {
+ return true;
+ }
+ else
+ {
+ SortedSet allChannels = new TreeSet<>(channels);
+ allChannels.add(channel);
+
+ //If the bandwidth of the channel set is less than or equal to the tuner's usable bandwidth, then
+ //check to see if we can find a valid center frequency
+ if(allChannels.last().getMaxFrequency() - allChannels.first().getMinFrequency() <= tunerController.getUsableBandwidth())
+ {
+ return getCenterFrequency(tunerController, allChannels) != INVALID_FREQUENCY;
+ }
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/source/tuner/manager/ChannelSourceManager.java b/src/main/java/io/github/dsheirer/source/tuner/manager/ChannelSourceManager.java
index 1b113313..3a3550df 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/manager/ChannelSourceManager.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/manager/ChannelSourceManager.java
@@ -54,9 +54,6 @@ public abstract class ChannelSourceManager implements ISourceEventProcessor
*/
public abstract TunerChannelSource getSource(TunerChannel tunerChannel);
- //TODO: remove this testing method when finished with polyphase channel manager
- public abstract TunerChannelSource getTestingSource(int index);
-
/**
* Adds a listener to receive source events
*/
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
new file mode 100644
index 00000000..8520146a
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/source/tuner/manager/HeterodyneChannelSourceManager.java
@@ -0,0 +1,236 @@
+/*******************************************************************************
+ * 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.manager;
+
+import io.github.dsheirer.dsp.filter.design.FilterDesignException;
+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.TunerChannel;
+import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Channel provider for heterodyne and decimate method of channel provisioning.
+ */
+public class HeterodyneChannelSourceManager extends ChannelSourceManager
+{
+ private final static Logger mLog = LoggerFactory.getLogger(HeterodyneChannelSourceManager.class);
+
+ private final static int OBJECTIVE_CHANNEL_SAMPLE_RATE = 30000;
+
+ private List mChannelSources = new CopyOnWriteArrayList<>();
+ private SortedSet mTunerChannels = new TreeSet<>();
+ private TunerController mTunerController;
+ private ChannelSourceEventProcessor mChannelSourceEventProcessor = new ChannelSourceEventProcessor();
+
+ public HeterodyneChannelSourceManager(TunerController tunerController)
+ {
+ mTunerController = tunerController;
+ mTunerController.addListener(this);
+ }
+
+ @Override
+ public SortedSet getTunerChannels()
+ {
+ return mTunerChannels;
+ }
+
+ @Override
+ public int getTunerChannelCount()
+ {
+ return mTunerChannels.size();
+ }
+
+ @Override
+ public TunerChannelSource getSource(TunerChannel tunerChannel)
+ {
+ if(CenterFrequencyCalculator.canTune(tunerChannel, mTunerController, mTunerChannels))
+ {
+ int decimation = mTunerController.getBandwidth() / OBJECTIVE_CHANNEL_SAMPLE_RATE;
+
+ try
+ {
+ //Attempt to create the channel source first, in case we get a filter design exception
+ CICTunerChannelSource2 tunerChannelSource = new CICTunerChannelSource2(mChannelSourceEventProcessor,
+ tunerChannel, mTunerController.getSampleRate(), decimation);
+
+ //Add to the list of channel sources so that it will receive the tuner frequency change
+ mChannelSources.add(tunerChannelSource);
+
+ //Set the current tuner frequency
+ tunerChannelSource.setFrequency(mTunerController.getFrequency());
+
+ //Add to the channel list and update the tuner center frequency as needed
+ mTunerChannels.add(tunerChannel);
+ updateTunerFrequency();
+
+ //Lock the tuner controller frequency and sample rate
+ mTunerController.setLocked(true);
+
+ broadcast(SourceEvent.channelCountChange(getTunerChannelCount()));
+
+ return tunerChannelSource;
+ }
+ catch(FilterDesignException fde)
+ {
+ mLog.error("Error creating CIC tuner channel source - couldn't design cleanup filter", fde);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Calculates a new center frequency and updates the tuner center frequency
+ */
+ private void updateTunerFrequency()
+ {
+ long centerFrequency = CenterFrequencyCalculator.getCenterFrequency(mTunerController, getTunerChannels());
+
+ if(centerFrequency == CenterFrequencyCalculator.INVALID_FREQUENCY)
+ {
+ mLog.error("Couldn't calculate center frequency for tuner and tuner channels");
+ return;
+ }
+
+ if(centerFrequency != mTunerController.getFrequency())
+ {
+ try
+ {
+ mTunerController.setFrequency(centerFrequency);
+ }
+ catch(SourceException se)
+ {
+ mLog.error("Couldn't update tuner center frequency to " + centerFrequency, se);
+ }
+ }
+ }
+
+ @Override
+ public void process(SourceEvent tunerSourceEvent) throws SourceException
+ {
+ switch(tunerSourceEvent.getEvent())
+ {
+ case NOTIFICATION_FREQUENCY_CHANGE:
+ //Tuner center frequency has changed - update channels
+ updateTunerFrequency(tunerSourceEvent.getValue().longValue());
+ break;
+ case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
+ //The tuner is self-correcting for PPM error - relay to channels
+ broadcastToChannels(tunerSourceEvent);
+ break;
+ case NOTIFICATION_SAMPLE_RATE_CHANGE:
+ case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_LOCKED:
+ case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_UNLOCKED:
+ //no-op
+ break;
+ default:
+ mLog.info("Unrecognized Source Event received from tuner: " + tunerSourceEvent);
+ }
+ }
+
+ /**
+ * Broadcasts the source event to any channel
+ */
+ private void broadcastToChannels(SourceEvent sourceEvent)
+ {
+ for(CICTunerChannelSource2 channelSource: mChannelSources)
+ {
+ try
+ {
+ channelSource.process(sourceEvent);
+ }
+ catch(Exception e)
+ {
+ mLog.error("Error broadcasting source event to channel: " + sourceEvent);
+ }
+ }
+ }
+
+ /**
+ * Updates all channel sources to use the new tuner center frequency
+ *
+ * @param tunerFrequency in hertz
+ */
+ private void updateTunerFrequency(long tunerFrequency)
+ {
+ for(CICTunerChannelSource2 channelSource : mChannelSources)
+ {
+ channelSource.setFrequency(tunerFrequency);
+ }
+ }
+
+ /**
+ * Processes channel source events
+ */
+ public class ChannelSourceEventProcessor implements Listener
+ {
+ @Override
+ public void receive(SourceEvent sourceEvent)
+ {
+ switch(sourceEvent.getEvent())
+ {
+ case REQUEST_START_SAMPLE_STREAM:
+ if(sourceEvent.getSource() instanceof CICTunerChannelSource2)
+ {
+ mTunerController.addBufferListener((CICTunerChannelSource2)sourceEvent.getSource());
+ }
+ break;
+ case REQUEST_STOP_SAMPLE_STREAM:
+ if(sourceEvent.getSource() instanceof CICTunerChannelSource2)
+ {
+ mTunerController.removeBufferListener((CICTunerChannelSource2)sourceEvent.getSource());
+ }
+ break;
+ case REQUEST_SOURCE_DISPOSE:
+ if(sourceEvent.getSource() instanceof CICTunerChannelSource2)
+ {
+ CICTunerChannelSource2 channelSource = (CICTunerChannelSource2)sourceEvent.getSource();
+ mChannelSources.remove(channelSource);
+ mTunerChannels.remove(channelSource.getTunerChannel());
+ channelSource.dispose();
+
+ //Unlock the tuner controller if there are no more channels
+ if(getTunerChannelCount() == 0)
+ {
+ mTunerController.setLocked(false);
+ }
+ broadcast(SourceEvent.channelCountChange(getTunerChannelCount()));
+ }
+ break;
+ case NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED:
+ //Rebroadcast so that the tuner source can process this event
+ broadcast(sourceEvent);
+ break;
+ case NOTIFICATION_CHANNEL_COUNT_CHANGE:
+ //Lock the tuner controller frequency & sample rate when we're processing channels
+ break;
+ default:
+ mLog.info("Unrecognized Source Event received from channel: " + sourceEvent);
+ break;
+ }
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/source/tuner/manager/TunerChannelSourceManager.java b/src/main/java/io/github/dsheirer/source/tuner/manager/TunerChannelSourceManager.java
index 9455f161..496f119f 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/manager/TunerChannelSourceManager.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/manager/TunerChannelSourceManager.java
@@ -49,13 +49,6 @@ public class TunerChannelSourceManager extends ChannelSourceManager
mTunerController.addListener(mPolyphaseChannelManager);
}
- @Override
- public TunerChannelSource getTestingSource(int index)
- {
- //TODO: remove this debug method
- return mPolyphaseChannelManager.getTestChannel(index);
- }
-
/**
* Indicates if the channel min/max frequencies are within the tunable frequency range of the tuner controller
*
diff --git a/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerController.java b/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerController.java
index 9b4fb200..9ba5b8d2 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerController.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerController.java
@@ -1241,7 +1241,7 @@ public abstract class RTL2832TunerController extends USBTunerController
RATE_1_200MHZ(0x0600, 1200000, "1.200 MHz"),
RATE_1_440MHZ(0x0500, 1440000, "1.440 MHz"),
RATE_1_600MHZ(0x0480, 1600000, "1.600 MHz"),
- RATE_1_800MHZ(0x0480, 1800000, "1.800 MHz"),
+ RATE_1_800MHZ(0x0400, 1800000, "1.800 MHz"),
RATE_1_920MHZ(0x03C0, 1920000, "1.920 MHz"),
RATE_2_048MHZ(0x0384, 2048000, "2.048 MHz"),
RATE_2_304MHZ(0x0320, 2304000, "2.304 MHz"),
diff --git a/src/main/java/io/github/dsheirer/source/tuner/test/TestTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/test/TestTunerController.java
index c2c8540f..51e00065 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/test/TestTunerController.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/test/TestTunerController.java
@@ -144,5 +144,6 @@ public class TestTunerController extends TunerController
public void setSampleRate(int sampleRate) throws SourceException
{
mSampleGenerator.setSampleRate(sampleRate);
+ mFrequencyController.setSampleRate(sampleRate);
}
}