diff --git a/src/main/java/io/github/dsheirer/dsp/filter/FilterFactory.java b/src/main/java/io/github/dsheirer/dsp/filter/FilterFactory.java index a0447635..1438104f 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/FilterFactory.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/FilterFactory.java @@ -121,6 +121,32 @@ public class FilterFactory return coefficients; } + /** + * Normalizes all filter coefficients to achieve an overall objective gain, by ensuring + * that the sum of the absolute value of all coefficients adds up to the objective. + * + * @param coefficients + * @return + */ + public static float[] normalize(float[] coefficients, float objective) + { + float accumulator = 0; + + for(int x = 0; x < coefficients.length; x++) + { + accumulator += Math.abs(coefficients[x]); + } + + accumulator /= objective; + + for(int x = 0; x < coefficients.length; x++) + { + coefficients[x] = coefficients[x] / accumulator; + } + + return coefficients; + } + /** * Constructs an array of unity (1) response values representing the * desired (pre-windowing) frequency response, used by the sync function @@ -740,8 +766,11 @@ public class FilterFactory public static float[] getSincM2Channelizer(double channelBandwidth, int channels, int tapsPerChannel, Window.WindowType windowType, boolean logResults) throws FilterDesignException { - return getSincFilter(channelBandwidth * channels, channelBandwidth, + float[] filter = getSincFilter(channelBandwidth * channels, channelBandwidth, channels, tapsPerChannel, windowType,logResults); + + //Increase the overall gain of the filter to an objective of 1.0 times the number of channels + return normalize(filter, 1.0f * (float)channels); } /** @@ -759,8 +788,11 @@ public class FilterFactory public static float[] getSincM2Synthesizer(double channelBandwidth, int channels, int tapsPerChannel, Window.WindowType windowType, boolean logResults) throws FilterDesignException { - return getSincFilter(channelBandwidth * channels, channelBandwidth, channels, + float[] filter = getSincFilter(channelBandwidth * channels, channelBandwidth, channels, tapsPerChannel, windowType,logResults); + + //Decrease the overall gain of the filter to an objective of 1.0 divided by the number of channels + return normalize(filter, 1.0f);// / (float)channels); } /** diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java index 660be28d..2e3af921 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java @@ -50,7 +50,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class); private static final double MINIMUM_CHANNEL_BANDWIDTH = 12500.0; private static final double CHANNEL_OVERSAMPLING = 2.0; - private static final int POLYPHASE_FILTER_TAPS_PER_CHANNEL = 16; + private static final int POLYPHASE_FILTER_TAPS_PER_CHANNEL = 8; private Broadcaster mSourceEventBroadcaster = new Broadcaster<>(); private IReusableBufferProvider mReusableBufferProvider; @@ -370,7 +370,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor //Broadcast the new channel sample rate double updatedChannelSampleRate = mChannelCalculator.getChannelSampleRate(); - updateOutputProcessors(SourceEvent.sampleRateChange(updatedChannelSampleRate)); + updateOutputProcessors(SourceEvent.sampleRateChange(updatedChannelSampleRate, "Channelizer Sample Rate Update")); } else { @@ -514,7 +514,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor if(taps == null) { taps = FilterFactory.getSincM2Synthesizer(mChannelCalculator.getChannelBandwidth(), channels, - POLYPHASE_FILTER_TAPS_PER_CHANNEL, Window.WindowType.BLACKMAN_HARRIS_7, true); + POLYPHASE_FILTER_TAPS_PER_CHANNEL + 20, Window.WindowType.BLACKMAN_HARRIS_7, true); mOutputProcessorFilters.put(channels, taps); } @@ -530,6 +530,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor @Override public void receive(SourceEvent sourceEvent) { + mLog.debug("****** Got a source event: " + sourceEvent.toString()); switch(sourceEvent.getEvent()) { case REQUEST_START_SAMPLE_STREAM: diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java index 19a09b36..4e41ffbe 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java @@ -199,7 +199,6 @@ public class PolyphaseChannelSource extends TunerChannelSource @Override protected void setFrequency(long frequency) { - mLog.debug("Index Center Frequency set to: " + frequency + " for channel " + getTunerChannel().toString()); mIndexCenterFrequency = frequency; //Set frequency correction to zero to trigger an update to the mixer and allow downstream monitors to @@ -218,11 +217,12 @@ public class PolyphaseChannelSource extends TunerChannelSource } /** - * Calculates the frequency offset required to mix the incoming signal to the desired output baseband + * Calculates the frequency offset required to mix the incoming signal to center the desired frequency + * within the channel */ private long getFrequencyOffset() { - return getTunerChannel().getFrequency() - mIndexCenterFrequency + mChannelFrequencyCorrection; + return mIndexCenterFrequency - getTunerChannel().getFrequency() + mChannelFrequencyCorrection; } /** diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/TwoChannelSynthesizerM2.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/TwoChannelSynthesizerM2.java index 093f682b..d0967a3e 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/TwoChannelSynthesizerM2.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/TwoChannelSynthesizerM2.java @@ -148,7 +148,7 @@ public class TwoChannelSynthesizerM2 try { - float[] taps = FilterFactory.getSincM2Channelizer(12500.0, 2, 19, Window.WindowType.BLACKMAN_HARRIS_7, true); + float[] taps = FilterFactory.getSincM2Synthesizer(12500.0, 2, 19, Window.WindowType.BLACKMAN_HARRIS_7, true); TwoChannelSynthesizerM2 synthesizer = new TwoChannelSynthesizerM2(taps); diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java index 44f9f8ea..f7c3a635 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java @@ -16,6 +16,10 @@ package io.github.dsheirer.dsp.filter.channelizer.output; import io.github.dsheirer.dsp.filter.channelizer.TwoChannelSynthesizerM2; +import io.github.dsheirer.dsp.filter.design.FilterDesignException; +import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification; +import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter; +import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner; import io.github.dsheirer.dsp.mixer.FS4DownConverter; import io.github.dsheirer.sample.buffer.ReusableBufferAssembler; import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer; @@ -26,10 +30,43 @@ import java.util.List; public class TwoChannelOutputProcessor extends ChannelOutputProcessor { + private final static Logger mLog = LoggerFactory.getLogger(TwoChannelOutputProcessor.class); + + + private static float[] mFilterCoefficients; + + static + { + try + { + FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder() + .sampleRate(25000) + .gridDensity(16) + .oddLength(true) + .passBandCutoff(6000) + .passBandAmplitude(1.0) + .passBandRipple(0.01) + .stopBandStart(6500) + .stopBandAmplitude(0.0) + .stopBandRipple(0.005) //Approximately 94 dB attenuation + .build(); + + RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification); + + if(designer.isValid()) + { + mFilterCoefficients = designer.getImpulseResponse(); + } + } + catch(FilterDesignException fde) + { + mLog.error("Error designing low pass filter for two channel output processor", fde); + } + } + private TwoChannelSynthesizerM2 mSynthesizer; private FS4DownConverter mFS4DownConverter = new FS4DownConverter(); - - private final static Logger mLog = LoggerFactory.getLogger(TwoChannelOutputProcessor.class); + private ComplexFIRFilter mLowPassFilter; private int mChannelOffset1; private int mChannelOffset2; @@ -45,9 +82,11 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor { //Set the frequency correction oscillator to 2 x output sample rate since we'll be correcting the frequency //after synthesizing both input channels - super(2, sampleRate * 2); + super(2, sampleRate); setPolyphaseChannelIndices(channelIndexes); setSynthesisFilter(filter); + + mLowPassFilter = new ComplexFIRFilter(mFilterCoefficients, 1.0f); } /** @@ -112,6 +151,8 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor //Apply offset and frequency correction to center the signal of interest within the synthesized channel getFrequencyCorrectionMixer().mixComplex(synthesized); + mLowPassFilter.filter(synthesized); + reusableBufferAssembler.receive(synthesized); buffer.decrementUserCount(); diff --git a/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java b/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java index 22354565..9fce71d0 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java @@ -1,7 +1,8 @@ package io.github.dsheirer.dsp.filter.design; +import io.github.dsheirer.dsp.filter.FilterFactory; +import io.github.dsheirer.dsp.filter.Window; import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification; -import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner; import javafx.application.Application; import javafx.scene.Scene; import javafx.stage.Stage; @@ -48,7 +49,7 @@ public class FilterViewer extends Application .passBandRipple(0.01) .stopBandStart(6500) .stopBandAmplitude(0.0) - .stopBandRipple(0.028) //Approximately 60 dB attenuation + .stopBandRipple(0.005) //Approximately 94 dB attenuation .build(); // FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder() @@ -73,29 +74,32 @@ public class FilterViewer extends Application float[] taps = null; -// try -// { -// taps = FilterFactory.getSincM2Channelizer(12500.0, 2, 19, Window.WindowType.BLACKMAN_HARRIS_7, true); -// } -// catch(FilterDesignException fde) -// { -// mLog.error("Error"); -// } - try { - RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification); - - if(designer.isValid()) - { - taps = designer.getImpulseResponse(); - } +// taps = FilterFactory.getSincM2Synthesizer(12500.0, 2, 19, Window.WindowType.BLACKMAN_HARRIS_7, true); + taps = FilterFactory.getSincFilter(25000.0, 12500.0, 2, 19, Window.WindowType.BLACKMAN_HARRIS_7, true); } catch(FilterDesignException fde) { - mLog.error("Filter design error", fde); + mLog.error("Error"); } +// try +// { +// RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification); +// +// if(designer.isValid()) +// { +// taps = designer.getImpulseResponse(); +// } +// } +// catch(FilterDesignException fde) +// { +// mLog.error("Filter design error", fde); +// } + + + return taps; } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/ComplexFIRFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/ComplexFIRFilter.java index ece55ce4..c4e3a909 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/ComplexFIRFilter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/ComplexFIRFilter.java @@ -26,6 +26,7 @@ public class ComplexFIRFilter extends FIRFilter private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("Complex FIR Filter"); private RealFIRFilter mIFilter; private RealFIRFilter mQFilter; + private float[] mTemporaryBuffer; /** * Complex FIR Filter for processing complex sample pairs. Wraps two real @@ -50,11 +51,21 @@ public class ComplexFIRFilter extends FIRFilter return mIFilter.filter(sample); } + public float currentInphaseValue() + { + return mIFilter.currentValue(); + } + public float filterQuadrature(float sample) { return mQFilter.filter(sample); } + public float currentQuadratureValue() + { + return mQFilter.currentValue(); + } + public Complex filter(Complex sample) { float i = filterInphase(sample.inphase()); @@ -65,15 +76,20 @@ public class ComplexFIRFilter extends FIRFilter public float[] filter(float[] samples) { - float[] filteredSamples = new float[samples.length]; + if(mTemporaryBuffer == null || mTemporaryBuffer.length != samples.length) + { + mTemporaryBuffer = new float[samples.length]; + } + + System.arraycopy(samples, 0, mTemporaryBuffer, 0, samples.length); for(int x = 0; x < samples.length; x += 2) { - filteredSamples[x] = filterInphase(samples[x]); - filteredSamples[x + 1] = filterQuadrature(samples[x + 1]); + samples[x] = filterInphase(mTemporaryBuffer[x]); + samples[x + 1] = filterQuadrature(mTemporaryBuffer[x + 1]); } - return filteredSamples; + return samples; } /** diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/real/RealFIRFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/RealFIRFilter.java index ec019bdc..1666de80 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/fir/real/RealFIRFilter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/RealFIRFilter.java @@ -26,6 +26,7 @@ public class RealFIRFilter extends FIRFilter private float[] mCoefficients; private float mGain; + private float mAccumulator; /** * Float sample FIR filter base class. @@ -84,12 +85,11 @@ public class RealFIRFilter extends FIRFilter { mBuffer[mBufferPointer] = sample; - float accumulator = 0.0f; + mAccumulator = 0.0f; for(int x = 0; x < mBufferSize; x++) { - accumulator += mCoefficients[x] * - mBuffer[mIndexMap[mBufferPointer][x]]; + mAccumulator += mCoefficients[x] * mBuffer[mIndexMap[mBufferPointer][x]]; } mBufferPointer--; @@ -100,7 +100,18 @@ public class RealFIRFilter extends FIRFilter } /* Apply gain and return the filtered value */ - return accumulator * mGain; + mAccumulator *= mGain; + + return mAccumulator; + } + + /** + * Current output value for the filter after the filter() method has been invoked. + * @return + */ + public float currentValue() + { + return mAccumulator; } /** diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/FS4DownConverter.java b/src/main/java/io/github/dsheirer/dsp/mixer/FS4DownConverter.java index 5c70593e..d1645698 100644 --- a/src/main/java/io/github/dsheirer/dsp/mixer/FS4DownConverter.java +++ b/src/main/java/io/github/dsheirer/dsp/mixer/FS4DownConverter.java @@ -15,17 +15,13 @@ ******************************************************************************/ package io.github.dsheirer.dsp.mixer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - public class FS4DownConverter { - private final static Logger mLog = LoggerFactory.getLogger(FS4DownConverter.class); - private int mPointer = 0; /** - * Performs complex down-sampling by one-fourth (-FS/4) of the sample rate using simple value assignments. + * Performs complex frequency conversion at one-fourth (-FS/4) of the sample rate using simple value + * assignments. * * See: Digital Signal Processing 3e, Lyons, p.674-675 */ diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ChannelizerViewer.java b/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer.java similarity index 98% rename from src/main/java/io/github/dsheirer/dsp/filter/channelizer/ChannelizerViewer.java rename to src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer.java index c6d6c25f..0537bf77 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ChannelizerViewer.java +++ b/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer.java @@ -1,7 +1,6 @@ /******************************************************************************* - * ********************************************************************************************************************* * sdr-trunk - * Copyright (C) 2014-2017 Dennis Sheirer + * 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 @@ -12,9 +11,9 @@ * * You should have received a copy of the GNU General Public License along with this program. * If not, see - * ********************************************************************************************************************* + * ******************************************************************************/ -package io.github.dsheirer.dsp.filter.channelizer; +package io.github.dsheirer.gui.channelizer; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.SampleType; diff --git a/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer2.java b/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer2.java new file mode 100644 index 00000000..47dfa93f --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer2.java @@ -0,0 +1,467 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see + * + ******************************************************************************/ +package io.github.dsheirer.gui.channelizer; + +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.sample.SampleType; +import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.settings.SettingsManager; +import io.github.dsheirer.source.ISourceEventProcessor; +import io.github.dsheirer.source.SourceEvent; +import io.github.dsheirer.source.SourceException; +import io.github.dsheirer.source.tuner.Tuner; +import io.github.dsheirer.source.tuner.channel.TunerChannel; +import io.github.dsheirer.source.tuner.channel.TunerChannelSource; +import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel; +import io.github.dsheirer.source.tuner.test.TestTuner; +import io.github.dsheirer.spectrum.DFTProcessor; +import io.github.dsheirer.spectrum.DFTSize; +import io.github.dsheirer.spectrum.SpectrumPanel; +import io.github.dsheirer.spectrum.converter.ComplexDecibelConverter; +import net.miginfocom.swing.MigLayout; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.JToggleButton; +import javax.swing.SpinnerNumberModel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class ChannelizerViewer2 extends JFrame +{ + private final static Logger mLog = LoggerFactory.getLogger(ChannelizerViewer2.class); + + private static final int CHANNEL_BANDWIDTH = 12500; + + private SettingsManager mSettingsManager = new SettingsManager(new TunerConfigurationModel()); + private JPanel mPrimaryPanel; + private JPanel mControlPanel; + private JButton mTopFrameAddChannelButton; + private JButton mBottomFrameAddChannelButton; + private JLabel mToneFrequencyLabel; + private JLabel mCenterFrequencyLabel; + private PrimarySpectrumPanel mPrimarySpectrumPanel; + private ChannelArrayPanel mTopChannelArrayPanel; + private ChannelArrayPanel mBottomChannelArrayPanel; + private DFTSize mMainPanelDFTSize = DFTSize.FFT32768; + private DFTSize mChannelPanelDFTSize = DFTSize.FFT04096; + private TestTuner mTestTuner = new TestTuner(); + + /** + * GUI Test utility for researching polyphase channelizers. + */ + public ChannelizerViewer2() + { + init(); + } + + private void init() + { + setTitle("Polyphase Channelizer Viewer"); + setSize(1200, 700); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]")); + setLocationRelativeTo(null); + add(getPrimaryPanel()); + } + + private JPanel getPrimaryPanel() + { + if(mPrimaryPanel == null) + { + mPrimaryPanel = new JPanel(); + mPrimaryPanel.setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[][][grow,fill][grow,fill]")); + mPrimaryPanel.add(getSpectrumPanel(), "wrap"); + mPrimaryPanel.add(getControlPanel(), "wrap"); + mPrimaryPanel.add(getTopChannelArrayPanel(), "wrap"); + mPrimaryPanel.add(getBottomChannelArrayPanel()); + } + + return mPrimaryPanel; + } + + private PrimarySpectrumPanel getSpectrumPanel() + { + if(mPrimarySpectrumPanel == null) + { + mPrimarySpectrumPanel = new PrimarySpectrumPanel(mSettingsManager, + mTestTuner.getTunerController().getSampleRate()); + mPrimarySpectrumPanel.setPreferredSize(new Dimension(1200, 200)); + mPrimarySpectrumPanel.setDFTSize(mMainPanelDFTSize); + mTestTuner.getTunerController().addBufferListener(mPrimarySpectrumPanel); + } + + return mPrimarySpectrumPanel; + } + + private JPanel getControlPanel() + { + if(mControlPanel == null) + { + mControlPanel = new JPanel(); + mControlPanel.setLayout(new MigLayout("insets 0 0 0 0", "", "")); + + mControlPanel.add(new JLabel("Tone:"), "align left"); + long minimumFrequency = -(long)mTestTuner.getTunerController().getSampleRate() / 2 + 1; + long maximumFrequency = (long)mTestTuner.getTunerController().getSampleRate() / 2 - 1; + long toneFrequency = 0; + + SpinnerNumberModel model = new SpinnerNumberModel(toneFrequency, minimumFrequency, maximumFrequency, + 100); + + model.addChangeListener(new ChangeListener() + { + @Override + public void stateChanged(ChangeEvent e) + { + long toneFrequency = model.getNumber().longValue(); + mTestTuner.getTunerController().setToneFrequency(toneFrequency); + mToneFrequencyLabel.setText(String.valueOf(getToneFrequency())); + mCenterFrequencyLabel.setText(String.valueOf(getCenterFrequency())); + } + }); + + JSpinner spinner = new JSpinner(model); + + mControlPanel.add(spinner); + mControlPanel.add(new JLabel("Hz")); + + mControlPanel.add(new JLabel("Tone Frequency:"), "push,align right"); + mToneFrequencyLabel = new JLabel(String.valueOf(getToneFrequency())); + mControlPanel.add(mToneFrequencyLabel, "push,align left"); + + mControlPanel.add(new JLabel("Center Frequency:"), "push,align right"); + mCenterFrequencyLabel = new JLabel(String.valueOf(getCenterFrequency())); + mControlPanel.add(mCenterFrequencyLabel, "push,align left"); + + mControlPanel.add(getBottomFrameAddChannelButton(), "push,align right"); + mControlPanel.add(getTopFrameAddChannelButton(), "push,align right"); + } + + return mControlPanel; + } + + private JButton getTopFrameAddChannelButton() + { + if(mTopFrameAddChannelButton == null) + { + mTopFrameAddChannelButton = new JButton("Top - Add Channel"); + + mTopFrameAddChannelButton.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + String value = JOptionPane.showInputDialog(ChannelizerViewer2.this, "Frequency?"); + + if(value != null && !value.isEmpty()) + { + try + { + int frequency = Integer.parseInt(value); + + TunerChannel tunerChannel = new TunerChannel(frequency, CHANNEL_BANDWIDTH); + + getTopChannelArrayPanel().addChannel(tunerChannel); + } + catch(Exception exception) + { + mLog.error("Can't parse frequency from value: " + value); + } + } + } + }); + } + + return mTopFrameAddChannelButton; + } + + private JButton getBottomFrameAddChannelButton() + { + if(mBottomFrameAddChannelButton == null) + { + mBottomFrameAddChannelButton = new JButton("Bottom - Add Channel"); + + mBottomFrameAddChannelButton.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + String value = JOptionPane.showInputDialog(ChannelizerViewer2.this, "Frequency?"); + + if(value != null && !value.isEmpty()) + { + try + { + int frequency = Integer.parseInt(value); + + TunerChannel tunerChannel = new TunerChannel(frequency, CHANNEL_BANDWIDTH); + + getBottomChannelArrayPanel().addChannel(tunerChannel); + } + catch(Exception exception) + { + mLog.error("Can't parse frequency from value: " + value); + } + } + } + }); + } + + return mBottomFrameAddChannelButton; + } + + private long getToneFrequency() + { + return mTestTuner.getTunerController().getFrequency() + mTestTuner.getTunerController().getToneFrequency(); + } + + private long getCenterFrequency() + { + return mTestTuner.getTunerController().getFrequency(); + } + + private ChannelArrayPanel getTopChannelArrayPanel() + { + if(mTopChannelArrayPanel == null) + { + mTopChannelArrayPanel = new ChannelArrayPanel(); + } + + return mTopChannelArrayPanel; + } + + private ChannelArrayPanel getBottomChannelArrayPanel() + { + if(mBottomChannelArrayPanel == null) + { + mBottomChannelArrayPanel = new ChannelArrayPanel(); + } + + return mBottomChannelArrayPanel; + } + + public class ChannelArrayPanel extends JPanel + { + public ChannelArrayPanel() + { + init(); + } + + private void init() + { + setLayout(new MigLayout("insets 0 0 0 0", "fill", "fill")); + } + + public void addChannel(TunerChannel tunerChannel) + { + ChannelPanel channelPanel = new ChannelPanel(mSettingsManager, CHANNEL_BANDWIDTH * 2, + tunerChannel.getFrequency(), CHANNEL_BANDWIDTH); + channelPanel.setDFTSize(mChannelPanelDFTSize); + + add(channelPanel, "grow,push"); + + validate(); + repaint(); + } + } + + /** + * Returns a list of tuner channels that will fit within the tuner's bandwidth, minus a half channel each at the + * lower and upper ends of the spectrum. + * + * @param tuner to create channels for + * @return list of contiguous channels filling the tuner bandwidth + */ + public static List getTunerChannels(Tuner tuner) + { + List tunerChannels = new ArrayList<>(); + + long baseFrequency = tuner.getTunerController().getFrequency(); + baseFrequency -= tuner.getTunerController().getSampleRate() / 2; + baseFrequency += (CHANNEL_BANDWIDTH / 2); + + int channelCount = (int)(tuner.getTunerController().getSampleRate() / CHANNEL_BANDWIDTH) - 1; + + for(int x = 0; x < channelCount; x++) + { + long frequency = baseFrequency + (x * CHANNEL_BANDWIDTH); + TunerChannel tunerChannel = new TunerChannel(frequency, CHANNEL_BANDWIDTH); + tunerChannels.add(tunerChannel); + } + + return tunerChannels; + } + + public class PrimarySpectrumPanel extends JPanel implements Listener, ISourceEventProcessor + { + private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX); + private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter(); + private SpectrumPanel mSpectrumPanel; + + public PrimarySpectrumPanel(SettingsManager settingsManager, double sampleRate) + { + setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]")); + mSpectrumPanel = new SpectrumPanel(settingsManager); + mSpectrumPanel.setSampleSize(16); + add(mSpectrumPanel); + + mDFTProcessor.addConverter(mComplexDecibelConverter); + mDFTProcessor.process(SourceEvent.sampleRateChange(sampleRate)); + mComplexDecibelConverter.addListener(mSpectrumPanel); + } + + public void setDFTSize(DFTSize dftSize) + { + mDFTProcessor.setDFTSize(dftSize); + } + + @Override + public void receive(ReusableComplexBuffer reusableComplexBuffer) + { + mDFTProcessor.receive(reusableComplexBuffer); + } + + @Override + public void process(SourceEvent event) throws SourceException + { + mLog.debug("Source Event! Add handler support for this to channelizer viewer"); + } + } + + public class ChannelPanel extends JPanel implements Listener, ISourceEventProcessor + { + private TunerChannelSource mSource; + private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX); + private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter(); + private SpectrumPanel mSpectrumPanel; + private JToggleButton mLoggingButton; + private boolean mLoggingEnabled; + + public ChannelPanel(SettingsManager settingsManager, double sampleRate, long frequency, int bandwidth) + { + setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill][grow,fill][grow,fill]", "[grow,fill][]")); + mSpectrumPanel = new SpectrumPanel(settingsManager); + mSpectrumPanel.setSampleSize(16); + add(mSpectrumPanel, "span"); + + mDFTProcessor.addConverter(mComplexDecibelConverter); + mDFTProcessor.process(SourceEvent.sampleRateChange(sampleRate)); + mComplexDecibelConverter.addListener(mSpectrumPanel); + + TunerChannel tunerChannel = new TunerChannel(frequency, bandwidth); + mSource = mTestTuner.getChannelSourceManager().getSource(tunerChannel); + + if(mSource != null) + { + mSource.setListener(new Listener() + { + @Override + public void receive(ReusableComplexBuffer complexBuffer) + { + if(mLoggingEnabled) + { + mLog.debug("Samples:" + Arrays.toString(complexBuffer.getSamples())); + } + + complexBuffer.incrementUserCount(); + mDFTProcessor.receive(complexBuffer); + complexBuffer.decrementUserCount(); + } + }); + + mSource.start(); + } + else + { + mLog.error("Couldn't get a source from the tuner for frequency: " + frequency); + } + + if(mSource != null) + { + int half = (int)(sampleRate / 2.0f); + add(new JLabel("Min:" + (frequency - half)), "align left"); + add(new JLabel("Center:" + frequency)); + add(new JLabel("Max:" + (frequency + half)), "align right"); + } + else + { + add(new JLabel("NO SRC:" + frequency)); + } + + mLoggingButton = new JToggleButton("Logging"); + mLoggingButton.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + mLoggingEnabled = mLoggingButton.isSelected(); + } + }); + } + + public TunerChannelSource getSource() + { + return mSource; + } + + public void setDFTSize(DFTSize dftSize) + { + mDFTProcessor.setDFTSize(dftSize); + } + + @Override + public void receive(ReusableComplexBuffer reusableComplexBuffer) + { + reusableComplexBuffer.incrementUserCount(); + mDFTProcessor.receive(reusableComplexBuffer); + reusableComplexBuffer.decrementUserCount(); + } + + @Override + public void process(SourceEvent event) throws SourceException + { + mLog.debug("Source Event! Add handler support for this to channelizer viewer"); + } + } + + public static void main(String[] args) + { + final ChannelizerViewer2 frame = new ChannelizerViewer2(); + + EventQueue.invokeLater(new Runnable() + { + @Override + public void run() + { + frame.setVisible(true); + } + }); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/SynthesizerViewer.java b/src/main/java/io/github/dsheirer/gui/channelizer/SynthesizerViewer.java similarity index 99% rename from src/main/java/io/github/dsheirer/dsp/filter/channelizer/SynthesizerViewer.java rename to src/main/java/io/github/dsheirer/gui/channelizer/SynthesizerViewer.java index ff8ae0d9..22b50402 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/SynthesizerViewer.java +++ b/src/main/java/io/github/dsheirer/gui/channelizer/SynthesizerViewer.java @@ -13,10 +13,11 @@ * If not, see * ******************************************************************************/ -package io.github.dsheirer.dsp.filter.channelizer; +package io.github.dsheirer.gui.channelizer; import io.github.dsheirer.dsp.filter.FilterFactory; import io.github.dsheirer.dsp.filter.Window; +import io.github.dsheirer.dsp.filter.channelizer.TwoChannelSynthesizerM2; import io.github.dsheirer.dsp.filter.design.FilterDesignException; import io.github.dsheirer.dsp.mixer.FS4DownConverter; import io.github.dsheirer.dsp.mixer.IOscillator; diff --git a/src/main/java/io/github/dsheirer/module/ProcessingChain.java b/src/main/java/io/github/dsheirer/module/ProcessingChain.java index 5beafede..25197f1b 100644 --- a/src/main/java/io/github/dsheirer/module/ProcessingChain.java +++ b/src/main/java/io/github/dsheirer/module/ProcessingChain.java @@ -564,12 +564,18 @@ public class ProcessingChain implements IChannelEventListener { if(mSource != null) { - /* Reset each of the modules */ + //Reset each of the modules for(Module module : mModules) { module.reset(); } + //Broadcast the source sample rate so that each of the modules can self-configure + mSourceEventBroadcaster.broadcast(SourceEvent.sampleRateChange(mSource.getSampleRate(), "Processing Chain Startup")); + + //Broadcast the source center frequencysample rate so that each of the modules can self-configure + mSourceEventBroadcaster.broadcast(SourceEvent.frequencyChange(mSource.getFrequency(), "Processing Chain Startup")); + //Setup the channel state to monitor source overflow conditions mSource.setOverflowListener(mChannelState); @@ -616,12 +622,6 @@ public class ProcessingChain implements IChannelEventListener { if(mRunning.compareAndSet(true, false)) { - /* Stop each of the modules */ - for(Module module : mModules) - { - module.stop(); - } - if(mSource != null) { removeModule(mSource); @@ -642,6 +642,12 @@ public class ProcessingChain implements IChannelEventListener } mSource = null; + } + + /* Stop each of the remaining modules */ + for(Module module : mModules) + { + module.stop(); } } } diff --git a/src/main/java/io/github/dsheirer/record/RecorderManager.java b/src/main/java/io/github/dsheirer/record/RecorderManager.java index 60dacef2..1d6e37c0 100644 --- a/src/main/java/io/github/dsheirer/record/RecorderManager.java +++ b/src/main/java/io/github/dsheirer/record/RecorderManager.java @@ -41,8 +41,7 @@ public class RecorderManager implements Listener { private static final Logger mLog = LoggerFactory.getLogger(RecorderManager.class); - public static final int AUDIO_SAMPLE_RATE = 8000; - public static final int BASEBAND_SAMPLE_RATE = 48000; + public static final float BASEBAND_SAMPLE_RATE = 25000.0f; //Default sample rate - source can override public static final long IDLE_RECORDER_REMOVAL_THRESHOLD = 6000; //6 seconds private Map mRecorders = new HashMap<>(); diff --git a/src/main/java/io/github/dsheirer/record/wave/ComplexBufferWaveRecorder.java b/src/main/java/io/github/dsheirer/record/wave/ComplexBufferWaveRecorder.java index ff68d8fb..977f2588 100644 --- a/src/main/java/io/github/dsheirer/record/wave/ComplexBufferWaveRecorder.java +++ b/src/main/java/io/github/dsheirer/record/wave/ComplexBufferWaveRecorder.java @@ -15,12 +15,14 @@ ******************************************************************************/ package io.github.dsheirer.record.wave; +import io.github.dsheirer.dsp.filter.channelizer.ContinuousReusableBufferProcessor; import io.github.dsheirer.module.Module; import io.github.dsheirer.sample.ConversionUtils; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableBufferDisposedListener; import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener; import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.source.ISourceEventListener; +import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.util.ThreadPool; import io.github.dsheirer.util.TimeStamp; import org.slf4j.Logger; @@ -30,33 +32,45 @@ import javax.sound.sampled.AudioFormat; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ScheduledFuture; +import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** * WAVE audio recorder module for recording complex (I&Q) samples to a wave file */ -public class ComplexBufferWaveRecorder extends Module implements IReusableComplexBufferListener, Listener +public class ComplexBufferWaveRecorder extends Module implements IReusableComplexBufferListener, + Listener, ISourceEventListener { private final static Logger mLog = LoggerFactory.getLogger(ComplexBufferWaveRecorder.class); - private WaveWriter mWriter; + private ContinuousReusableBufferProcessor mBufferProcessor = + new ContinuousReusableBufferProcessor<>(500, 50); + + private AtomicBoolean mRunning = new AtomicBoolean(); + private ReusableBufferWaveWriter mWriter; private String mFilePrefix; private Path mFile; private AudioFormat mAudioFormat; - private BufferProcessor mBufferProcessor; - private ScheduledFuture mProcessorHandle; - private LinkedBlockingQueue mBuffers = new LinkedBlockingQueue<>(500); - - private AtomicBoolean mRunning = new AtomicBoolean(); - - public ComplexBufferWaveRecorder(int sampleRate, String filePrefix) + public ComplexBufferWaveRecorder(float sampleRate, String filePrefix) { - mAudioFormat = new AudioFormat(sampleRate, 16, 2, true, false); mFilePrefix = filePrefix; + setSampleRate(sampleRate); + } + + public void setSampleRate(float sampleRate) + { + if(mAudioFormat == null || mAudioFormat.getSampleRate() != sampleRate) + { + mAudioFormat = new AudioFormat(sampleRate, 16, 2, true, false); + + if(mRunning.get()) + { + stop(); + start(); + } + } } public Path getFile() @@ -68,11 +82,6 @@ public class ComplexBufferWaveRecorder extends Module implements IReusableComple { if(mRunning.compareAndSet(false, true)) { - if(mBufferProcessor == null) - { - mBufferProcessor = new BufferProcessor(); - } - try { StringBuilder sb = new StringBuilder(); @@ -83,11 +92,10 @@ public class ComplexBufferWaveRecorder extends Module implements IReusableComple mFile = Paths.get(sb.toString()); - mWriter = new WaveWriter(mAudioFormat, mFile); + mWriter = new ReusableBufferWaveWriter(mAudioFormat, mFile); - /* Schedule the processor to run every 500 milliseconds */ - mProcessorHandle = ThreadPool.SCHEDULED.scheduleAtFixedRate( - mBufferProcessor, 0, 500, TimeUnit.MILLISECONDS); + mBufferProcessor.setListener(mWriter); + mBufferProcessor.start(); } catch(IOException io) { @@ -100,25 +108,40 @@ public class ComplexBufferWaveRecorder extends Module implements IReusableComple { if(mRunning.compareAndSet(true, false)) { - receive(new PoisonPill()); + if(mBufferProcessor != null) + { + mBufferProcessor.stop(); + mBufferProcessor.setListener(null); + } + + if(mWriter != null) + { + //Thread this operation so that it doesn't tie up the calling thread. The wave writer + //close method will also rename the file and this can sometimes take a few seconds. + ThreadPool.SCHEDULED.schedule(new Runnable() + { + @Override + public void run() + { + try + { + mWriter.close(); + } + catch(IOException ioe) + { + mLog.error("Error closing baseband I/Q recorder", ioe); + } + } + }, 0, TimeUnit.MILLISECONDS); + } } } @Override public void receive(ReusableComplexBuffer buffer) { - if(mRunning.get()) - { - boolean success = mBuffers.offer(buffer); - - if(!success) - { - mLog.error("recorder buffer overflow - purging [" + - mFile.toFile().getAbsolutePath() + "]"); - - mBuffers.clear(); - } - } + //Queue the buffer with the buffer processor so that recording occurs on the buffer processor thread + mBufferProcessor.receive(buffer); } @Override @@ -138,83 +161,57 @@ public class ComplexBufferWaveRecorder extends Module implements IReusableComple { } - public class BufferProcessor implements Runnable + @Override + public Listener getSourceEventListener() { - public void run() + return new Listener() { - try + @Override + public void receive(SourceEvent sourceEvent) { - ReusableComplexBuffer reusableComplexBuffer = mBuffers.poll(); - - while(reusableComplexBuffer != null) + switch(sourceEvent.getEvent()) { - if(reusableComplexBuffer instanceof PoisonPill) - { - //Clear the buffer queue - reusableComplexBuffer = mBuffers.poll(); - - while(reusableComplexBuffer != null) - { - reusableComplexBuffer.decrementUserCount(); - reusableComplexBuffer = mBuffers.poll(); - } - - if(mWriter != null) - { - try - { - mWriter.close(); - mWriter = null; - } - catch(IOException io) - { - mLog.error("Error stopping complex wave recorder [" + getFile() + "]", io); - } - } - - mFile = null; - - if(mProcessorHandle != null) - { - mProcessorHandle.cancel(true); - } - - mProcessorHandle = null; - } - else - { - mWriter.writeData(ConversionUtils.convertToSigned16BitSamples(reusableComplexBuffer)); - reusableComplexBuffer.decrementUserCount(); - reusableComplexBuffer = mBuffers.poll(); - } + case NOTIFICATION_SAMPLE_RATE_CHANGE: + setSampleRate(sourceEvent.getValue().floatValue()); + break; } } - catch(IOException ioe) - { - /* Stop this module if/when we get an IO exception */ - mBuffers.clear(); - stop(); - - mLog.error("IOException while trying to write to the wave writer", ioe); - } - } + }; } /** - * This is used as a sentinel value to signal the buffer processor to end + * Wave writer implementation for reusable complex buffers delivered from buffer processor */ - public class PoisonPill extends ReusableComplexBuffer + public class ReusableBufferWaveWriter extends WaveWriter implements Listener> { - public PoisonPill() + public ReusableBufferWaveWriter(AudioFormat format, Path file) throws IOException { - super(new IReusableBufferDisposedListener() + super(format, file); + } + + @Override + public void receive(List reusableComplexBuffers) + { + boolean error = false; + + for(ReusableComplexBuffer reusableComplexBuffer: reusableComplexBuffers) { - @Override - public void disposed(ReusableComplexBuffer reusableComplexBuffer) + if(!error) { - //no-op + try + { + mWriter.writeData(ConversionUtils.convertToSigned16BitSamples(reusableComplexBuffer)); + } + catch(IOException ioe) + { + mLog.error("IOException while writing I/Q buffers to wave recorder - stopping recorder", ioe); + error = true; + stop(); + } } - }, new float[2]); + + reusableComplexBuffer.decrementUserCount(); + } } } } diff --git a/src/main/java/io/github/dsheirer/record/wave/WaveWriter.java b/src/main/java/io/github/dsheirer/record/wave/WaveWriter.java index 15c9abaa..9a7dd5f3 100644 --- a/src/main/java/io/github/dsheirer/record/wave/WaveWriter.java +++ b/src/main/java/io/github/dsheirer/record/wave/WaveWriter.java @@ -133,7 +133,6 @@ public class WaveWriter implements AutoCloseable { mFileChannel.force(true); mFileChannel.close(); - mFileChannel = null; rename(); } diff --git a/src/main/java/io/github/dsheirer/source/SourceEvent.java b/src/main/java/io/github/dsheirer/source/SourceEvent.java index c8b6996e..405fbb85 100644 --- a/src/main/java/io/github/dsheirer/source/SourceEvent.java +++ b/src/main/java/io/github/dsheirer/source/SourceEvent.java @@ -49,6 +49,17 @@ public class SourceEvent private Event mEvent; private Number mValue; private Source mSource; + private String mEventDescription; + + /** + * Private constructor. Use the static constructor methods to create an event. + */ + private SourceEvent(Event event, Number value, String eventDescription) + { + mEvent = event; + mValue = value; + mEventDescription = eventDescription; + } /** * Private constructor. Use the static constructor methods to create an event. @@ -147,6 +158,17 @@ public class SourceEvent return new SourceEvent(Event.NOTIFICATION_FREQUENCY_CHANGE, frequency); } + /** + * Creates a new frequency change event + * + * @param frequency in hertz + * @param eventDescription to add a tag to the event + */ + public static SourceEvent frequencyChange(long frequency, String eventDescription) + { + return new SourceEvent(Event.NOTIFICATION_FREQUENCY_CHANGE, frequency, eventDescription); + } + /** * Creates a new frequency correction change event * @@ -177,6 +199,17 @@ public class SourceEvent return new SourceEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE, sampleRate); } + /** + * Creates a new sample rate change event + * + * @param sampleRate in hertz + * @param eventDescription to include with the event + */ + public static SourceEvent sampleRateChange(double sampleRate, String eventDescription) + { + return new SourceEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE, sampleRate, eventDescription); + } + /** * Creates a new channel sample rate change event * @@ -250,8 +283,12 @@ public class SourceEvent return new SourceEvent(Event.REQUEST_SOURCE_DISPOSE, source); } + @Override public String toString() { - return "SOURCE EVENT:" + mEvent + " VALUE:" + (mValue != null ? mValue : "(empty)"); + return "SOURCE EVENT:" + mEvent + + " VALUE:" + (mValue != null ? mValue : "(empty)") + + " SOURCE:" + (mSource != null ? mSource.getClass() : "(null)") + + " DESCRIPTION:" + (mEventDescription != null ? mEventDescription : ""); } } diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java b/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java index 76c6590b..d012be06 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java +++ b/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java @@ -118,7 +118,6 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc { //Broadcast current frequency and sample rate so consumer can configure correctly broadcastConsumerSourceEvent(SourceEvent.frequencyChange(getFrequency())); - broadcastConsumerSourceEvent(SourceEvent.sampleRateChange(getSampleRate())); if(mProducerSourceEventListener != null) { diff --git a/src/main/java/io/github/dsheirer/source/tuner/test/TestTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/test/TestTunerController.java index fc2489f2..8d9a809c 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/test/TestTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/test/TestTunerController.java @@ -32,7 +32,7 @@ public class TestTunerController extends TunerController public static final long MINIMUM_FREQUENCY = 1l; public static final long MAXIMUM_FREQUENCY = 1000000000l; - public static final int SAMPLE_RATE = 100000; + public static final int SAMPLE_RATE = 10000000; public static final int DC_NOISE_BANDWIDTH = 0; public static final double USABLE_BANDWIDTH_PERCENTAGE = 1.00;