mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-14 17:47:32 -05:00
Merge pull request #402 from DSheirer/heterodyne_channelizer
401 Update Tuners to use Heterodyne Channelizer
This commit is contained in:
@@ -148,6 +148,8 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
|
|||||||
mTrafficChannelCallEvent = null;
|
mTrafficChannelCallEvent = null;
|
||||||
|
|
||||||
mSquelchLocked = false;
|
mSquelchLocked = false;
|
||||||
|
|
||||||
|
setState(State.TEARDOWN);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispose()
|
public void dispose()
|
||||||
|
|||||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||||
|
*
|
||||||
|
******************************************************************************/
|
||||||
package io.github.dsheirer.controller.channel;
|
package io.github.dsheirer.controller.channel;
|
||||||
|
|
||||||
import com.jidesoft.swing.JideSplitPane;
|
import com.jidesoft.swing.JideSplitPane;
|
||||||
@@ -24,8 +39,7 @@ import java.awt.Rectangle;
|
|||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
|
|
||||||
public class ChannelController extends JPanel
|
public class ChannelController extends JPanel implements ActionListener, ListSelectionListener
|
||||||
implements ActionListener, ListSelectionListener
|
|
||||||
{
|
{
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
@@ -168,9 +182,13 @@ public class ChannelController extends JPanel
|
|||||||
if( index >= 0 )
|
if( index >= 0 )
|
||||||
{
|
{
|
||||||
int translatedIndex = mChannelTable.convertRowIndexToView( index );
|
int translatedIndex = mChannelTable.convertRowIndexToView( index );
|
||||||
mChannelTable.setRowSelectionInterval( translatedIndex, translatedIndex );
|
|
||||||
mChannelTable.scrollRectToVisible(
|
if(translatedIndex >= 0)
|
||||||
new Rectangle( mChannelTable.getCellRect( translatedIndex, 0, true ) ) );
|
{
|
||||||
|
mChannelTable.setRowSelectionInterval( translatedIndex, translatedIndex );
|
||||||
|
mChannelTable.scrollRectToVisible(
|
||||||
|
new Rectangle( mChannelTable.getCellRect( translatedIndex, 0, true ) ) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package io.github.dsheirer.dsp.filter;
|
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.design.FilterDesignException;
|
||||||
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||||
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
||||||
@@ -358,15 +357,6 @@ public class FilterFactory
|
|||||||
return (int)(Math.round((double)attenuation / (22.0d * frequency)));
|
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.
|
* Determines decimation rate(s) for a polyphase decimation filter.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1719,7 +1719,32 @@ public enum Filters
|
|||||||
0.011873357051047719f, 0.0f, -0.007901319195893647f, 0.0f,
|
0.011873357051047719f, 0.0f, -0.007901319195893647f, 0.0f,
|
||||||
0.005055504379767936f, 0.0f, -0.003054430179754289f, 0.0f,
|
0.005055504379767936f, 0.0f, -0.003054430179754289f, 0.0f,
|
||||||
0.001695637278417295f, 0.0f, -0.000998606272947510f
|
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;
|
float[] mCoefficients;
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -172,36 +171,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
|||||||
return channelSource;
|
return channelSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TunerChannelSource getTestChannel(int index)
|
|
||||||
{
|
|
||||||
PolyphaseChannelSource channelSource = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
List<Integer> 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
|
* Creates a processor to process the channelizer channel indexes into a composite output stream providing
|
||||||
* channelized complex sample buffers to a registered source listener.
|
* channelized complex sample buffers to a registered source listener.
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
|||||||
* @param frequency in hertz
|
* @param frequency in hertz
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void setFrequency(long frequency)
|
public void setFrequency(long frequency)
|
||||||
{
|
{
|
||||||
mIndexCenterFrequency = frequency;
|
mIndexCenterFrequency = frequency;
|
||||||
|
|
||||||
|
|||||||
@@ -15,11 +15,10 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package io.github.dsheirer.dsp.filter.cic;
|
package io.github.dsheirer.dsp.filter.cic;
|
||||||
|
|
||||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||||
import io.github.dsheirer.dsp.filter.Filters;
|
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||||
import io.github.dsheirer.dsp.filter.Window;
|
|
||||||
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2;
|
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.Listener;
|
||||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||||
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
|
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 org.apache.commons.lang3.Validate;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
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<ReusableComplexBuffer>
|
public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
||||||
{
|
{
|
||||||
|
// 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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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<DecimatingStage> mDecimatingStages =
|
private static Map<Integer,float[]> sLowPassFilters = new HashMap();
|
||||||
new ArrayList<DecimatingStage>();
|
private static Map<Integer,List<Integer>> sPrimeFactors = new HashMap();
|
||||||
|
|
||||||
|
private List<DecimatingStage> mDecimatingStages = new ArrayList<DecimatingStage>();
|
||||||
private DecimatingStage mFirstDecimatingStage;
|
private DecimatingStage mFirstDecimatingStage;
|
||||||
|
|
||||||
private Output mOutput;
|
private Output mOutput;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Non-Recursive Prime-Factor CIC Filter with float sample array inputs and
|
* Constructs a new decimation filter.
|
||||||
* decimated, single paired i/q float sample output.
|
|
||||||
*
|
*
|
||||||
* Implements the CIC filter described in Understanding Digital Signal
|
* An example setup for a 2.4 MHz input sample rate and 25 kHz output channel rate with a desired 12.5 kHz channel
|
||||||
* Processing, 3e, Lyons, on page 769. This filter is comprised of multiple
|
* pass band might be:
|
||||||
* decimating stages each with a prime factor decimation rate. Multiple
|
* sampleRate = 2,400,000.0
|
||||||
* stages are cascaded to achieve the overall decimation rate.
|
* decimation = 96
|
||||||
|
* passFrequency = 6250 (1/4 of the channel rate)
|
||||||
|
* stopFrequency = 7000
|
||||||
*
|
*
|
||||||
* This filter supports a maximum decimation rate of 700. This filter can
|
* Note: the transition bandwidth (stop - start) directly impacts the size of the low-pass cleanup filter,
|
||||||
* be adapted to higher decimation rates by adding additional prime factors
|
* where smaller transition bandwidth creates a longer filter that requires more processing effort.
|
||||||
* to the PRIMES array.
|
|
||||||
*
|
*
|
||||||
|
* @param sampleRate of the input sample stream
|
||||||
* @param decimation - overall decimation rate
|
* @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,
|
public ComplexPrimeCICDecimate(double sampleRate, int decimation, int passFrequency, int stopFrequency)
|
||||||
int passFrequency, int attenuation, Window.WindowType windowType)
|
throws FilterDesignException
|
||||||
{
|
{
|
||||||
Validate.isTrue(decimation <= 700);
|
Validate.isTrue(decimation <= PRIMES[PRIMES.length - 1]);
|
||||||
|
|
||||||
|
|
||||||
List<Integer> stageSizes = getPrimeFactors(decimation);
|
List<Integer> stageSizes = getPrimeFactors(decimation);
|
||||||
|
|
||||||
for(int x = 0; x < stageSizes.size(); x++)
|
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);
|
mDecimatingStages.add(stage);
|
||||||
|
|
||||||
@@ -89,10 +129,11 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mOutput = new Output(48000, passFrequency, attenuation, windowType);
|
double channelRate = sampleRate / (double)decimation;
|
||||||
|
|
||||||
mDecimatingStages.get(mDecimatingStages.size() - 1)
|
mOutput = new Output(channelRate, passFrequency, stopFrequency);
|
||||||
.setListener(mOutput);
|
|
||||||
|
mDecimatingStages.get(mDecimatingStages.size() - 1).setListener(mOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispose()
|
public void dispose()
|
||||||
@@ -127,16 +168,26 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the prime factors of the decimation rate up to a maximum
|
* Calculates the prime factors of the decimation rate. If you wish to have decimation rates higher than the
|
||||||
* decimation rate of 700. If you wish to have decimation rates higher
|
* highest rate listed in the PRIMES array, then add additional prime factors to the PRIMES array.
|
||||||
* than 700, then add additional prime factors to the PRIMES array.
|
|
||||||
*
|
*
|
||||||
* @param decimation - integral decimation rate
|
* @param decimation - integral decimation rate
|
||||||
* @return - ordered list (smallest to largest) of prime factors
|
* @return - ordered list (smallest to largest) of prime factors
|
||||||
*/
|
*/
|
||||||
public static List<Integer> getPrimeFactors(int decimation)
|
public static List<Integer> getPrimeFactors(int decimation)
|
||||||
{
|
{
|
||||||
ArrayList<Integer> stages = new ArrayList<Integer>();
|
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<Integer> primeFactors = new ArrayList<Integer>();
|
||||||
|
|
||||||
int pointer = 0;
|
int pointer = 0;
|
||||||
|
|
||||||
@@ -146,8 +197,7 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
|||||||
|
|
||||||
if(decimation % prime == 0)
|
if(decimation % prime == 0)
|
||||||
{
|
{
|
||||||
stages.add(prime);
|
primeFactors.add(prime);
|
||||||
|
|
||||||
decimation /= prime;
|
decimation /= prime;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -156,7 +206,9 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return stages;
|
sPrimeFactors.put(decimation, primeFactors);
|
||||||
|
|
||||||
|
return primeFactors;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -185,7 +237,7 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
|||||||
*/
|
*/
|
||||||
public class DecimatingStage implements ComplexSampleListener
|
public class DecimatingStage implements ComplexSampleListener
|
||||||
{
|
{
|
||||||
private ArrayList<Stage> mStages = new ArrayList<Stage>();
|
private List<Stage> mStages = new ArrayList<Stage>();
|
||||||
private Stage mFirstStage;
|
private Stage mFirstStage;
|
||||||
private ComplexDecimator mDecimator;
|
private ComplexDecimator mDecimator;
|
||||||
|
|
||||||
@@ -217,7 +269,6 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
|||||||
}
|
}
|
||||||
|
|
||||||
mDecimator = new ComplexDecimator(size);
|
mDecimator = new ComplexDecimator(size);
|
||||||
|
|
||||||
mStages.get(mStages.size() - 1).setListener(mDecimator);
|
mStages.get(mStages.size() - 1).setListener(mDecimator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,9 +299,8 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Single non-decimating CIC stage component. Uses a circular buffer and a
|
* Single non-decimating CIC stage component. Uses a circular buffer and a running average internally to implement
|
||||||
* running average internally to implement the stage so that stage size has
|
* the stage so that stage size has essentially no impact on the computational requirements of the stage
|
||||||
* essentially no impact on the computational requirements of the stage
|
|
||||||
*/
|
*/
|
||||||
public class Stage implements ComplexSampleListener
|
public class Stage implements ComplexSampleListener
|
||||||
{
|
{
|
||||||
@@ -267,7 +317,7 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
|||||||
|
|
||||||
protected float mGain;
|
protected float mGain;
|
||||||
|
|
||||||
public Stage()
|
protected Stage()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,7 +328,8 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
|||||||
mISamples = new float[mSize];
|
mISamples = new float[mSize];
|
||||||
mQSamples = 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()
|
public void dispose()
|
||||||
@@ -288,7 +339,7 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
|||||||
|
|
||||||
public void receive(float i, float q)
|
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;
|
mISum = mISum - mISamples[mSamplePointer] + i;
|
||||||
mQSum = mQSum - mQSamples[mSamplePointer] + q;
|
mQSum = mQSum - mQSamples[mSamplePointer] + q;
|
||||||
|
|
||||||
@@ -316,30 +367,24 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Size 2 stage that removes the unnecessary circular buffer management for
|
* Size 2 stage that eliminates the unnecessary circular buffer management for a two-stage.
|
||||||
* a two-stage.
|
|
||||||
*/
|
*/
|
||||||
public class TwoStage extends Stage
|
public class TwoStage extends Stage
|
||||||
{
|
{
|
||||||
public TwoStage()
|
public TwoStage()
|
||||||
{
|
{
|
||||||
super();
|
|
||||||
|
|
||||||
mGain = 0.5f;
|
mGain = 0.5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void receive(float i, float q)
|
public void receive(float i, float q)
|
||||||
{
|
{
|
||||||
float iSum = mISum + i;
|
if(mListener != null)
|
||||||
float qSum = mQSum + q;
|
{
|
||||||
|
mListener.receive(((mISum + i) * mGain), ((mQSum + q) * mGain));
|
||||||
|
}
|
||||||
|
|
||||||
mISum = i;
|
mISum = i;
|
||||||
mQSum = q;
|
mQSum = q;
|
||||||
|
|
||||||
if(mListener != null)
|
|
||||||
{
|
|
||||||
mListener.receive((iSum * mGain), (qSum * mGain));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,31 +395,29 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
|||||||
*/
|
*/
|
||||||
public class Output implements ComplexSampleListener
|
public class Output implements ComplexSampleListener
|
||||||
{
|
{
|
||||||
/* Decimated output buffers will contain 1024 samples */
|
/* Decimated output buffers will contain 1024 complex samples */
|
||||||
private ReusableComplexBufferAssembler mAssembler = new ReusableComplexBufferAssembler(2048, 48000.0);
|
private ReusableComplexBufferAssembler mBufferAssembler;
|
||||||
|
private ComplexFIRFilter2 mLowPassFilter;
|
||||||
private ComplexFIRFilter2 mCleanupFilter;
|
|
||||||
private ComplexHalfBandFilter mHalfBandFilter = new ComplexHalfBandFilter(
|
|
||||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false);
|
|
||||||
private Listener<ReusableComplexBuffer> mReusableComplexBufferListener;
|
private Listener<ReusableComplexBuffer> mReusableComplexBufferListener;
|
||||||
|
|
||||||
public Output(int outputSampleRate, int passFrequency, int attenuation,
|
public Output(double outputSampleRate, int passFrequency, int stopFrequency) throws FilterDesignException
|
||||||
Window.WindowType windowType)
|
|
||||||
{
|
{
|
||||||
mCleanupFilter = new ComplexFIRFilter2(FilterFactory
|
mBufferAssembler = new ReusableComplexBufferAssembler(2400, outputSampleRate);
|
||||||
.getCICCleanupFilter(outputSampleRate, passFrequency, attenuation, windowType), 0.4f);
|
|
||||||
|
|
||||||
//Bypassing the CIC cleanup filter for now
|
//This may throw an exception if we can't design a filter for the sample rate and pass/stop frequencies
|
||||||
mAssembler.setListener(new Listener<ReusableComplexBuffer>()
|
float[] filterCoefficients = getLowPassFilter(outputSampleRate, passFrequency, stopFrequency);
|
||||||
|
mLowPassFilter = new ComplexFIRFilter2(filterCoefficients, 1.0f);
|
||||||
|
|
||||||
|
mBufferAssembler.setListener(new Listener<ReusableComplexBuffer>()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void receive(ReusableComplexBuffer reusableComplexBuffer)
|
public void receive(ReusableComplexBuffer reusableComplexBuffer)
|
||||||
{
|
{
|
||||||
if(mReusableComplexBufferListener != null)
|
if(mReusableComplexBufferListener != null)
|
||||||
{
|
{
|
||||||
ReusableComplexBuffer cleanupFiltered = mCleanupFilter.filter(reusableComplexBuffer);
|
ReusableComplexBuffer filteredBuffer = mLowPassFilter.filter(reusableComplexBuffer);
|
||||||
ReusableComplexBuffer halfBandFiltered = mHalfBandFilter.filter(cleanupFiltered);
|
mReusableComplexBufferListener.receive(filteredBuffer);
|
||||||
mReusableComplexBufferListener.receive(halfBandFiltered);
|
// mReusableComplexBufferListener.receive(reusableComplexBuffer);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -386,20 +429,17 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
|||||||
|
|
||||||
public void dispose()
|
public void dispose()
|
||||||
{
|
{
|
||||||
mAssembler.dispose();
|
mBufferAssembler.dispose();
|
||||||
mCleanupFilter.dispose();
|
mLowPassFilter.dispose();
|
||||||
mHalfBandFilter.dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Receiver method for the output adapter to receive a filtered,
|
* Interface for receiving CIC decimated output samples
|
||||||
* decimated sample, apply gain correction, apply cleanup filtering
|
|
||||||
* and output the sample values as a complex sample.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void receive(float inphase, float quadrature)
|
public void receive(float inphase, float quadrature)
|
||||||
{
|
{
|
||||||
mAssembler.receive(new float[]{inphase, quadrature});
|
mBufferAssembler.receive(inphase, quadrature);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setListener(Listener<ReusableComplexBuffer> listener)
|
public void setListener(Listener<ReusableComplexBuffer> listener)
|
||||||
@@ -411,5 +451,43 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
|||||||
{
|
{
|
||||||
mReusableComplexBufferListener = null;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,92 +15,94 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package io.github.dsheirer.dsp.filter.halfband.complex;
|
package io.github.dsheirer.dsp.filter.halfband.complex;
|
||||||
|
|
||||||
import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter;
|
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.ReusableComplexBuffer;
|
||||||
|
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
|
||||||
|
|
||||||
public class ComplexHalfBandFilter
|
public class ComplexHalfBandFilter
|
||||||
{
|
{
|
||||||
private HalfBandFilter mIFilter;
|
private ReusableComplexBufferQueue mBufferQueue = new ReusableComplexBufferQueue("ComplexHalfBandFilter");
|
||||||
private HalfBandFilter mQFilter;
|
|
||||||
private boolean mDecimate;
|
|
||||||
private boolean mDecimateFlag;
|
|
||||||
|
|
||||||
/**
|
private HalfBandFilter2 mIFilter;
|
||||||
* @param coefficients
|
private HalfBandFilter2 mQFilter;
|
||||||
* @param gain
|
private float mResidualISample;
|
||||||
* @param decimate
|
private float mResidualQSample;
|
||||||
*/
|
private boolean mHasResidual;
|
||||||
public ComplexHalfBandFilter(float[] coefficients, float gain, boolean decimate)
|
private int mOutputBufferLength;
|
||||||
|
private float[] mSamples;
|
||||||
|
private int mSamplesPointer;
|
||||||
|
private float[] mFilteredSamples;
|
||||||
|
private int mFilteredSamplesPointer;
|
||||||
|
|
||||||
|
public ComplexHalfBandFilter(float[] coefficients, float gain)
|
||||||
{
|
{
|
||||||
mIFilter = new HalfBandFilter(coefficients, gain);
|
mIFilter = new HalfBandFilter2(coefficients, gain);
|
||||||
mQFilter = new HalfBandFilter(coefficients, gain);
|
mQFilter = new HalfBandFilter2(coefficients, gain);
|
||||||
mDecimate = decimate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispose()
|
public ReusableComplexBuffer filter(ReusableComplexBuffer originalBuffer)
|
||||||
{
|
{
|
||||||
mIFilter.dispose();
|
mSamples = originalBuffer.getSamples();
|
||||||
mQFilter.dispose();
|
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 ReusableComplexBuffer filter(ReusableComplexBuffer buffer)
|
public static void main(String[] args)
|
||||||
{
|
{
|
||||||
throw new IllegalStateException("This filter has to be updated to work with reusable buffers");
|
Oscillator oscillator = new Oscillator(1, 16);
|
||||||
|
ComplexHalfBandFilter filter = new ComplexHalfBandFilter(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();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// if(mDecimate)
|
|
||||||
// {
|
|
||||||
// float[] samples = buffer.getSamples();
|
|
||||||
//
|
|
||||||
// int half = samples.length / 2;
|
|
||||||
//
|
|
||||||
// float[] decimated;
|
|
||||||
//
|
|
||||||
// if(half % 2 == 0 || mDecimateFlag)
|
|
||||||
// {
|
|
||||||
// decimated = new float[half];
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// /* If inbound buffer size is odd-length, then we have to
|
|
||||||
// * adjust when the first operation is non-decimation, since
|
|
||||||
// * that will produce an outbound buffer 1 sample larger */
|
|
||||||
// decimated = new float[half + 1];
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// int decimatedPointer = 0;
|
|
||||||
//
|
|
||||||
// for(int x = 0; x < samples.length; x += 2)
|
|
||||||
// {
|
|
||||||
// /* Insert the sample but don't filter */
|
|
||||||
// if(mDecimateFlag)
|
|
||||||
// {
|
|
||||||
// mIFilter.insert(samples[x]);
|
|
||||||
// mQFilter.insert(samples[x + 1]);
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// decimated[decimatedPointer++] = mIFilter.filter(samples[x]);
|
|
||||||
// decimated[decimatedPointer++] = mQFilter.filter(samples[x + 1]);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /* Toggle the decimation flag for every sample */
|
|
||||||
// mDecimateFlag = !mDecimateFlag;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// //return buffer
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// /* Non Decimate - filter each of the values and return the buffer */
|
|
||||||
// float[] samples = buffer.getSamples();
|
|
||||||
//
|
|
||||||
// for(int x = 0; x < samples.length; x += 2)
|
|
||||||
// {
|
|
||||||
// samples[x] = mIFilter.filter(samples[x]);
|
|
||||||
// samples[x + 1] = mQFilter.filter(samples[x + 1]);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// //return buffer
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
|
|||||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public class HalfBandFilter
|
public class HalfBandFilter
|
||||||
{
|
{
|
||||||
private float[] mBuffer;
|
private float[] mBuffer;
|
||||||
|
|||||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||||
|
*
|
||||||
|
******************************************************************************/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -127,8 +127,8 @@ public abstract class AbstractOscillator implements IOscillator
|
|||||||
|
|
||||||
for(int x = 0; x < sampleCount; x++)
|
for(int x = 0; x < sampleCount; x++)
|
||||||
{
|
{
|
||||||
samples[x] = inphase();
|
|
||||||
rotate();
|
rotate();
|
||||||
|
samples[x] = quadrature();
|
||||||
}
|
}
|
||||||
|
|
||||||
return samples;
|
return samples;
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ package io.github.dsheirer.dsp.mixer;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class LowPhaseNoiseOscillator extends AbstractOscillator
|
public class LowPhaseNoiseOscillator extends AbstractOscillator
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(LowPhaseNoiseOscillator.class);
|
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
|
//Update the gain value for the next rotation
|
||||||
mGain = THREE_HALVES - ((mPreviousInphase * mPreviousInphase) + (mPreviousQuadrature * mPreviousQuadrature));
|
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))));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -259,7 +259,8 @@ public class ChannelizerViewer extends JFrame
|
|||||||
|
|
||||||
for(int x = 0; x < mChannelCount; x++)
|
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);
|
DiscreteChannelPanel channelPanel = new DiscreteChannelPanel(mSettingsManager, source, x);
|
||||||
channelPanel.setDFTSize(mChannelPanelDFTSize);
|
channelPanel.setDFTSize(mChannelPanelDFTSize);
|
||||||
|
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ public class WaveWriter implements AutoCloseable
|
|||||||
|
|
||||||
int suffix = 1;
|
int suffix = 1;
|
||||||
|
|
||||||
while(Files.exists(renamedPath))
|
while(Files.exists(renamedPath) && suffix < 4)
|
||||||
{
|
{
|
||||||
String renamedWithSuffix = mFile.getFileName().toString();
|
String renamedWithSuffix = mFile.getFileName().toString();
|
||||||
renamedWithSuffix = renamedWithSuffix.replace(".tmp", "_" + suffix + ".wav");
|
renamedWithSuffix = renamedWithSuffix.replace(".tmp", "_" + suffix + ".wav");
|
||||||
|
|||||||
@@ -63,6 +63,29 @@ public class ReusableComplexBufferAssembler
|
|||||||
mListener = null;
|
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
|
* Adds the samples to this assembler. As each buffer is assembled, it will be dispatched to the registered
|
||||||
* listener.
|
* listener.
|
||||||
|
|||||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||||
|
*
|
||||||
|
******************************************************************************/
|
||||||
package io.github.dsheirer.sample.decimator;
|
package io.github.dsheirer.sample.decimator;
|
||||||
|
|
||||||
import io.github.dsheirer.sample.complex.ComplexSampleListener;
|
import io.github.dsheirer.sample.complex.ComplexSampleListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decimator for complex samples
|
||||||
|
*/
|
||||||
public class ComplexDecimator implements ComplexSampleListener
|
public class ComplexDecimator implements ComplexSampleListener
|
||||||
{
|
{
|
||||||
private ComplexSampleListener mListener;
|
private ComplexSampleListener mListener;
|
||||||
private int mCounter = 0;
|
private int mCounter = 0;
|
||||||
private int mDecimationRate;
|
private int mDecimationRate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new Decimator object with the specified decimation rate.
|
* Constructs a new Decimator object with the specified decimation rate.
|
||||||
*/
|
*/
|
||||||
public ComplexDecimator( int 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 )
|
|
||||||
{
|
{
|
||||||
mCounter++;
|
mDecimationRate = rate;
|
||||||
|
|
||||||
if( mCounter >= mDecimationRate )
|
|
||||||
{
|
|
||||||
mListener.receive( i, q );
|
|
||||||
|
|
||||||
mCounter = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void dispose()
|
||||||
* Sets the decimated output listener
|
{
|
||||||
*/
|
mListener = null;
|
||||||
public void setListener( ComplexSampleListener listener )
|
}
|
||||||
{
|
|
||||||
mListener = listener;
|
/**
|
||||||
}
|
* 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import io.github.dsheirer.source.ISourceEventProcessor;
|
|||||||
import io.github.dsheirer.source.SourceEvent;
|
import io.github.dsheirer.source.SourceEvent;
|
||||||
import io.github.dsheirer.source.tuner.TunerEvent.Event;
|
import io.github.dsheirer.source.tuner.TunerEvent.Event;
|
||||||
import io.github.dsheirer.source.tuner.manager.ChannelSourceManager;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -51,7 +51,9 @@ public abstract class Tuner implements ISourceEventProcessor
|
|||||||
//Register to receive frequency and sample rate change notifications
|
//Register to receive frequency and sample rate change notifications
|
||||||
mTunerController.addListener(this::process);
|
mTunerController.addListener(this::process);
|
||||||
|
|
||||||
mChannelSourceManager = new TunerChannelSourceManager(mTunerController);
|
// mChannelSourceManager = new PolyphaseChannelSourceManager(mTunerController);
|
||||||
|
mChannelSourceManager = new HeterodyneChannelSourceManager(mTunerController);
|
||||||
|
|
||||||
//Register to receive channel count change notifications
|
//Register to receive channel count change notifications
|
||||||
mChannelSourceManager.addSourceEventListener(this::process);
|
mChannelSourceManager.addSourceEventListener(this::process);
|
||||||
|
|
||||||
|
|||||||
@@ -289,15 +289,7 @@ public class TunerModel extends AbstractTableModel implements Listener<TunerEven
|
|||||||
}
|
}
|
||||||
case CHANNEL_COUNT:
|
case CHANNEL_COUNT:
|
||||||
int channelCount = tuner.getChannelSourceManager().getTunerChannelCount();
|
int channelCount = tuner.getChannelSourceManager().getTunerChannelCount();
|
||||||
|
return channelCount + " (" + (tuner.getTunerController().isLocked() ? "LOCKED)" : "UNLOCKED)");
|
||||||
if(channelCount > 0)
|
|
||||||
{
|
|
||||||
return channelCount + " (LOCKED)";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return "0";
|
|
||||||
}
|
|
||||||
case FREQUENCY_ERROR:
|
case FREQUENCY_ERROR:
|
||||||
double ppm = tuner.getTunerController().getFrequencyCorrection();
|
double ppm = tuner.getTunerController().getFrequencyCorrection();
|
||||||
return mFrequencyErrorPPMFormat.format(ppm);
|
return mFrequencyErrorPPMFormat.format(ppm);
|
||||||
|
|||||||
@@ -15,26 +15,28 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package io.github.dsheirer.source.tuner.channel;
|
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.cic.ComplexPrimeCICDecimate;
|
||||||
|
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||||
import io.github.dsheirer.dsp.mixer.IOscillator;
|
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.Listener;
|
||||||
import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue;
|
import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue;
|
||||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||||
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
|
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
|
||||||
import io.github.dsheirer.sample.complex.Complex;
|
import io.github.dsheirer.sample.complex.Complex;
|
||||||
import io.github.dsheirer.source.tuner.Tuner;
|
import io.github.dsheirer.source.SourceEvent;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
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<ReusableComplexBuffer>
|
||||||
{
|
{
|
||||||
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
|
//Maximum number of filled buffers for the blocking queue
|
||||||
private static final int BUFFER_MAX_CAPACITY = 300;
|
private static final int BUFFER_MAX_CAPACITY = 300;
|
||||||
@@ -42,8 +44,11 @@ public class CICTunerChannelSource
|
|||||||
//Threshold for resetting buffer overflow condition
|
//Threshold for resetting buffer overflow condition
|
||||||
private static final int BUFFER_OVERFLOW_RESET_THRESHOLD = 100;
|
private static final int BUFFER_OVERFLOW_RESET_THRESHOLD = 100;
|
||||||
|
|
||||||
private static double CHANNEL_RATE = 48000.0;
|
//Pass band cutoff frequency for the complex channel - target = 12.5 kHz channel
|
||||||
private static int CHANNEL_PASS_FREQUENCY = 12000;
|
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<ReusableComplexBuffer> mBuffer;
|
private OverflowableReusableBufferTransferQueue<ReusableComplexBuffer> mBuffer;
|
||||||
private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("CICTunerChannelSource");
|
private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("CICTunerChannelSource");
|
||||||
@@ -51,51 +56,66 @@ public class CICTunerChannelSource
|
|||||||
private ComplexPrimeCICDecimate mDecimationFilter;
|
private ComplexPrimeCICDecimate mDecimationFilter;
|
||||||
private Listener<ReusableComplexBuffer> mListener;
|
private Listener<ReusableComplexBuffer> mListener;
|
||||||
private List<ReusableComplexBuffer> mSampleBuffers = new ArrayList<>();
|
private List<ReusableComplexBuffer> mSampleBuffers = new ArrayList<>();
|
||||||
private long mTunerFrequency = 0;
|
private double mChannelSampleRate;
|
||||||
private double mTunerSampleRate;
|
|
||||||
private long mChannelFrequencyCorrection = 0;
|
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
|
* Constructs a frequency translating and CIC decimating channel source.
|
||||||
* fixed 48 kHz IQ channel rate.
|
|
||||||
*
|
*
|
||||||
* Note: this class can only be used once (started and stopped) and a new tuner channel source must be requested
|
* @param producerSourceEventListener to receive sample stream start/stop requests
|
||||||
* from the tuner once this object has been stopped. This is because channels are managed dynamically and center
|
* @param tunerChannel that details the desired channel frequency and bandwidth
|
||||||
* tuned frequency may have changed since this source was obtained and thus the tuner might no longer be able to
|
* @param sampleRate of the incoming sample stream
|
||||||
* source this channel once it has been stopped.
|
* @param decimation to use in the CIC decimation filter.
|
||||||
*
|
* @throws FilterDesignException if a final cleanup filter cannot be designed using the remez filter
|
||||||
* @param tunerChannel specifying the center frequency for the DDC
|
* designer and the filter parameters.
|
||||||
*/
|
*/
|
||||||
public CICTunerChannelSource(Tuner tuner, TunerChannel tunerChannel)
|
public CICTunerChannelSource(Listener<SourceEvent> 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 = 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
|
mFrequencyCorrectionMixer = new LowPhaseNoiseOscillator(frequencyOffset, sampleRate);
|
||||||
mTunerFrequency = tuner.getTunerController().getFrequency();
|
}
|
||||||
long frequencyOffset = mTunerFrequency - tunerChannel.getFrequency();
|
|
||||||
mFrequencyCorrectionMixer = new Oscillator(frequencyOffset, tuner.getTunerController().getSampleRate());
|
|
||||||
|
|
||||||
//Setup the decimation filter chain
|
@Override
|
||||||
setSampleRate(tuner.getTunerController().getSampleRate());
|
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
|
* @param frequency in hertz
|
||||||
*/
|
*/
|
||||||
protected void setFrequency(long frequency)
|
@Override
|
||||||
|
public void setFrequency(long frequency)
|
||||||
{
|
{
|
||||||
mTunerFrequency = frequency;
|
mTunerFrequency = frequency;
|
||||||
|
|
||||||
//Reset frequency correction so that consumer components can adjust
|
|
||||||
setFrequencyCorrection(0);
|
|
||||||
|
|
||||||
updateMixerFrequencyOffset();
|
updateMixerFrequencyOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current frequency correction being applied to the channel sample stream
|
* Current frequency correction being applied to the channel sample stream
|
||||||
|
*
|
||||||
* @return correction in hertz
|
* @return correction in hertz
|
||||||
*/
|
*/
|
||||||
public long getFrequencyCorrection()
|
public long getFrequencyCorrection()
|
||||||
@@ -105,61 +125,14 @@ public class CICTunerChannelSource
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes the frequency correction value and broadcasts the change to the registered downstream listener.
|
* Changes the frequency correction value and broadcasts the change to the registered downstream listener.
|
||||||
|
*
|
||||||
* @param correction current frequency correction value.
|
* @param correction current frequency correction value.
|
||||||
*/
|
*/
|
||||||
protected void setFrequencyCorrection(long correction)
|
public void setFrequencyCorrection(long correction)
|
||||||
{
|
{
|
||||||
mChannelFrequencyCorrection = correction;
|
mChannelFrequencyCorrection = correction;
|
||||||
|
|
||||||
updateMixerFrequencyOffset();
|
updateMixerFrequencyOffset();
|
||||||
|
broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection));
|
||||||
// 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<ReusableComplexBuffer> 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<ReusableComplexBuffer> 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()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -168,46 +141,97 @@ public class CICTunerChannelSource
|
|||||||
*/
|
*/
|
||||||
private void updateMixerFrequencyOffset()
|
private void updateMixerFrequencyOffset()
|
||||||
{
|
{
|
||||||
// long offset = mTunerFrequency - getTunerChannel().getFrequency() - mChannelFrequencyCorrection;
|
long offset = mTunerFrequency - getTunerChannel().getFrequency() - mChannelFrequencyCorrection;
|
||||||
// mFrequencyCorrectionMixer.setFrequency(offset);
|
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<ReusableComplexBuffer> complexBufferListener)
|
||||||
|
{
|
||||||
|
mDecimationFilter.setListener(complexBufferListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeListener(Listener<ReusableComplexBuffer> listener)
|
||||||
|
{
|
||||||
|
mDecimationFilter.removeListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
public double getSampleRate()
|
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()
|
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);
|
ReusableComplexBuffer translatedComplexBuffer = mReusableComplexBufferQueue.getBuffer(samples.length);
|
||||||
float[] translated = reusableComplexBuffer.getSamples();
|
float[] translatedSamples = translatedComplexBuffer.getSamples();
|
||||||
|
|
||||||
/* Perform frequency translation */
|
/* Perform frequency translation */
|
||||||
for(int x = 0; x < samples.length; x += 2)
|
for(int x = 0; x < samples.length; x += 2)
|
||||||
{
|
{
|
||||||
mFrequencyCorrectionMixer.rotate();
|
mFrequencyCorrectionMixer.rotate();
|
||||||
|
|
||||||
translated[x] = Complex.multiplyInphase(
|
translatedSamples[x] = Complex.multiplyInphase(samples[x], samples[x + 1],
|
||||||
samples[x], samples[x + 1], mFrequencyCorrectionMixer.inphase(), mFrequencyCorrectionMixer.quadrature());
|
mFrequencyCorrectionMixer.inphase(), mFrequencyCorrectionMixer.quadrature());
|
||||||
|
|
||||||
translated[x + 1] = Complex.multiplyQuadrature(
|
translatedSamples[x + 1] = Complex.multiplyQuadrature(samples[x], samples[x + 1],
|
||||||
samples[x], samples[x + 1], mFrequencyCorrectionMixer.inphase(), mFrequencyCorrectionMixer.quadrature());
|
mFrequencyCorrectionMixer.inphase(), mFrequencyCorrectionMixer.quadrature());
|
||||||
}
|
}
|
||||||
|
|
||||||
final ComplexPrimeCICDecimate filter = mDecimationFilter;
|
mDecimationFilter.receive(translatedComplexBuffer);
|
||||||
|
complexBuffer.decrementUserCount();
|
||||||
if(filter != null)
|
|
||||||
{
|
|
||||||
filter.receive(reusableComplexBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.decrementUserCount();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mSampleBuffers.clear();
|
mSampleBuffers.clear();
|
||||||
|
|||||||
@@ -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.
|
* Sets the center frequency for the sample streaming being sent from the producer.
|
||||||
* @param frequency in hertz
|
* @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
|
* 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
|
//Broadcast current frequency and sample rate so consumer can configure correctly
|
||||||
broadcastConsumerSourceEvent(SourceEvent.frequencyChange(getFrequency()));
|
broadcastConsumerSourceEvent(SourceEvent.frequencyChange(getFrequency()));
|
||||||
|
broadcastProducerSourceEvent(SourceEvent.startSampleStreamRequest(this));
|
||||||
if(mProducerSourceEventListener != null)
|
|
||||||
{
|
|
||||||
mProducerSourceEventListener.receive(SourceEvent.startSampleStreamRequest(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
mScheduledIntervalProcessor.start();
|
mScheduledIntervalProcessor.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,12 +128,8 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
|
|||||||
*/
|
*/
|
||||||
public void stop()
|
public void stop()
|
||||||
{
|
{
|
||||||
if(mProducerSourceEventListener != null)
|
broadcastProducerSourceEvent(SourceEvent.stopSampleStreamRequest(this));
|
||||||
{
|
broadcastProducerSourceEvent(SourceEvent.sourceDisposeRequest(this));
|
||||||
mProducerSourceEventListener.receive(SourceEvent.stopSampleStreamRequest(this));
|
|
||||||
mProducerSourceEventListener.receive(SourceEvent.sourceDisposeRequest(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
mScheduledIntervalProcessor.stop();
|
mScheduledIntervalProcessor.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,6 +162,7 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
|
|||||||
{
|
{
|
||||||
switch(sourceEvent.getEvent())
|
switch(sourceEvent.getEvent())
|
||||||
{
|
{
|
||||||
|
case NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
|
||||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||||
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_LOCKED:
|
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_LOCKED:
|
||||||
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_UNLOCKED:
|
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_UNLOCKED:
|
||||||
|
|||||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||||
|
*
|
||||||
|
******************************************************************************/
|
||||||
|
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<TunerChannel> 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<TunerChannel> 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<TunerChannel> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -54,9 +54,6 @@ public abstract class ChannelSourceManager implements ISourceEventProcessor
|
|||||||
*/
|
*/
|
||||||
public abstract TunerChannelSource getSource(TunerChannel tunerChannel);
|
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
|
* Adds a listener to receive source events
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||||
|
*
|
||||||
|
******************************************************************************/
|
||||||
|
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.CICTunerChannelSource;
|
||||||
|
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<CICTunerChannelSource> mChannelSources = new CopyOnWriteArrayList<>();
|
||||||
|
private SortedSet<TunerChannel> mTunerChannels = new TreeSet<>();
|
||||||
|
private TunerController mTunerController;
|
||||||
|
private ChannelSourceEventProcessor mChannelSourceEventProcessor = new ChannelSourceEventProcessor();
|
||||||
|
|
||||||
|
public HeterodyneChannelSourceManager(TunerController tunerController)
|
||||||
|
{
|
||||||
|
mTunerController = tunerController;
|
||||||
|
mTunerController.addListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SortedSet<TunerChannel> 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
|
||||||
|
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
|
||||||
|
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(CICTunerChannelSource 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(CICTunerChannelSource channelSource : mChannelSources)
|
||||||
|
{
|
||||||
|
channelSource.setFrequency(tunerFrequency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes channel source events
|
||||||
|
*/
|
||||||
|
public class ChannelSourceEventProcessor implements Listener<SourceEvent>
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void receive(SourceEvent sourceEvent)
|
||||||
|
{
|
||||||
|
switch(sourceEvent.getEvent())
|
||||||
|
{
|
||||||
|
case REQUEST_START_SAMPLE_STREAM:
|
||||||
|
if(sourceEvent.getSource() instanceof CICTunerChannelSource)
|
||||||
|
{
|
||||||
|
mTunerController.addBufferListener((CICTunerChannelSource)sourceEvent.getSource());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case REQUEST_STOP_SAMPLE_STREAM:
|
||||||
|
if(sourceEvent.getSource() instanceof CICTunerChannelSource)
|
||||||
|
{
|
||||||
|
mTunerController.removeBufferListener((CICTunerChannelSource)sourceEvent.getSource());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case REQUEST_SOURCE_DISPOSE:
|
||||||
|
if(sourceEvent.getSource() instanceof CICTunerChannelSource)
|
||||||
|
{
|
||||||
|
CICTunerChannelSource channelSource = (CICTunerChannelSource)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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,20 +26,20 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import java.util.SortedSet;
|
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 PolyphaseChannelManager mPolyphaseChannelManager;
|
||||||
private TunerController mTunerController;
|
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
|
* 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.
|
* 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
|
* @param tunerController with a center tuned frequency that will be managed by this instance
|
||||||
*/
|
*/
|
||||||
public TunerChannelSourceManager(TunerController tunerController)
|
public PolyphaseChannelSourceManager(TunerController tunerController)
|
||||||
{
|
{
|
||||||
mTunerController = tunerController;
|
mTunerController = tunerController;
|
||||||
|
|
||||||
@@ -49,13 +49,6 @@ public class TunerChannelSourceManager extends ChannelSourceManager
|
|||||||
mTunerController.addListener(mPolyphaseChannelManager);
|
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
|
* Indicates if the channel min/max frequencies are within the tunable frequency range of the tuner controller
|
||||||
*
|
*
|
||||||
@@ -1241,7 +1241,7 @@ public abstract class RTL2832TunerController extends USBTunerController
|
|||||||
RATE_1_200MHZ(0x0600, 1200000, "1.200 MHz"),
|
RATE_1_200MHZ(0x0600, 1200000, "1.200 MHz"),
|
||||||
RATE_1_440MHZ(0x0500, 1440000, "1.440 MHz"),
|
RATE_1_440MHZ(0x0500, 1440000, "1.440 MHz"),
|
||||||
RATE_1_600MHZ(0x0480, 1600000, "1.600 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_1_920MHZ(0x03C0, 1920000, "1.920 MHz"),
|
||||||
RATE_2_048MHZ(0x0384, 2048000, "2.048 MHz"),
|
RATE_2_048MHZ(0x0384, 2048000, "2.048 MHz"),
|
||||||
RATE_2_304MHZ(0x0320, 2304000, "2.304 MHz"),
|
RATE_2_304MHZ(0x0320, 2304000, "2.304 MHz"),
|
||||||
|
|||||||
@@ -144,5 +144,6 @@ public class TestTunerController extends TunerController
|
|||||||
public void setSampleRate(int sampleRate) throws SourceException
|
public void setSampleRate(int sampleRate) throws SourceException
|
||||||
{
|
{
|
||||||
mSampleGenerator.setSampleRate(sampleRate);
|
mSampleGenerator.setSampleRate(sampleRate);
|
||||||
|
mFrequencyController.setSampleRate(sampleRate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user