diff --git a/.idea/dictionaries/denny.xml b/.idea/dictionaries/denny.xml index 015bac87..30d94b58 100644 --- a/.idea/dictionaries/denny.xml +++ b/.idea/dictionaries/denny.xml @@ -5,6 +5,8 @@ broadcastify esnid fleetsync + heterdyned + heterodynes icecast ident nutall diff --git a/src/main/java/io/github/dsheirer/audio/invert/AudioAdapter.java b/src/main/java/io/github/dsheirer/audio/invert/AudioAdapter.java index 090c002c..d3e1d269 100644 --- a/src/main/java/io/github/dsheirer/audio/invert/AudioAdapter.java +++ b/src/main/java/io/github/dsheirer/audio/invert/AudioAdapter.java @@ -20,6 +20,7 @@ package io.github.dsheirer.audio.invert; import io.github.dsheirer.dsp.filter.FilterFactory; import io.github.dsheirer.dsp.filter.FloatFIRFilter; import io.github.dsheirer.dsp.filter.Window.WindowType; +import io.github.dsheirer.dsp.mixer.IOscillator; import io.github.dsheirer.dsp.mixer.Oscillator; /** @@ -29,7 +30,7 @@ import io.github.dsheirer.dsp.mixer.Oscillator; public class AudioAdapter { private static int mGain = 5; - private Oscillator mSineWaveGenerator; + private IOscillator mSineWaveGenerator; private FloatFIRFilter mPostInversionFilter; private FloatFIRFilter mAudioHighPassFilter; private AudioType mAudioType = AudioType.NORMAL; @@ -88,7 +89,7 @@ public class AudioAdapter default: retVal = (float)( mPostInversionFilter.get( mAudioHighPassFilter.get( sample ) * - mSineWaveGenerator.getFloat() ) ); + mSineWaveGenerator.inphase() ) ); mSineWaveGenerator.rotate(); break; diff --git a/src/main/java/io/github/dsheirer/audio/invert/AudioInverter.java b/src/main/java/io/github/dsheirer/audio/invert/AudioInverter.java index a892b23c..63b6b6d9 100644 --- a/src/main/java/io/github/dsheirer/audio/invert/AudioInverter.java +++ b/src/main/java/io/github/dsheirer/audio/invert/AudioInverter.java @@ -20,6 +20,7 @@ package io.github.dsheirer.audio.invert; import io.github.dsheirer.audio.InversionFrequency; import io.github.dsheirer.dsp.filter.Filters; import io.github.dsheirer.dsp.filter.FloatFIRFilter; +import io.github.dsheirer.dsp.mixer.IOscillator; import io.github.dsheirer.dsp.mixer.Oscillator; import io.github.dsheirer.sample.real.RealSampleListener; @@ -29,7 +30,7 @@ import io.github.dsheirer.sample.real.RealSampleListener; */ public class AudioInverter implements RealSampleListener { - private Oscillator mSineWaveGenerator; + private IOscillator mSineWaveGenerator; private FloatFIRFilter mPostInversionLowPassFilter; private RealSampleListener mListener; @@ -61,7 +62,7 @@ public class AudioInverter implements RealSampleListener if( mPostInversionLowPassFilter != null ) { mPostInversionLowPassFilter.receive( - sample * mSineWaveGenerator.getFloat() ); + sample * mSineWaveGenerator.inphase() ); mSineWaveGenerator.rotate(); } diff --git a/src/main/java/io/github/dsheirer/buffer/CircularBuffer.java b/src/main/java/io/github/dsheirer/buffer/CircularBuffer.java index fd92b870..86ff4b0e 100644 --- a/src/main/java/io/github/dsheirer/buffer/CircularBuffer.java +++ b/src/main/java/io/github/dsheirer/buffer/CircularBuffer.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see + * + ******************************************************************************/ package io.github.dsheirer.buffer; import io.github.dsheirer.sample.Listener; @@ -8,87 +23,86 @@ import java.util.List; public class CircularBuffer implements Listener { - private ArrayList mElements; - private Listener mListener; - private int mPointer; - private int mSize; + private List mElements; + private Listener mListener; + private int mPointer; + private int mSize; - /** - * Circular Buffer - stores elements in an array list. Once the buffer is - * full, new samples replace older samples and older samples are broadcast - * to an optional listener. - * - * @param size - size of buffer - */ - public CircularBuffer( int size ) - { - mSize = size; - mElements = new ArrayList(); - } + /** + * Circular Buffer - stores elements in an array list. Once the buffer is + * full, new samples replace older samples and older samples are broadcast + * to an optional listener. + * + * @param size - size of buffer + */ + public CircularBuffer(int size) + { + mSize = size; + mElements = new ArrayList(); + } - /** - * Returns the element stored at index. Note: the index argument refers to - * the internal storage index and does not indicate a temporal relationship - * among elements. For example, if you get indexes 1, 2, and 3, you may be - * getting ( oldest - 1, oldest, newest ) elements, if the internal pointer - * is currently pointing to and will insert the next element at index 4. - * - * @param index - element index - * - * @return element or null - */ - public T get( int index ) - { - if( index < mElements.size() ) - { - return mElements.get( index ); - } - - return null; - } - - public int getSize() - { - return mSize; - } - - public List getElements() - { - return Collections.unmodifiableList( mElements ); - } - - @Override - public void receive( T element ) - { - T previous = null; - - if( mElements.size() > mPointer ) - { - previous = mElements.get( mPointer ); - - mElements.set( mPointer, element ); - } - else - { - mElements.add( element ); - } - - mPointer++; - - if( mPointer >= mSize ) - { - mPointer = 0; - } - - if( previous != null && mListener != null ) - { - mListener.receive( previous ); - } - } + /** + * Returns the element stored at index. Note: the index argument refers to + * the internal storage index and does not indicate a temporal relationship + * among elements. For example, if you get indexes 1, 2, and 3, you may be + * getting ( oldest - 1, oldest, newest ) elements, if the internal pointer + * is currently pointing to and will insert the next element at index 4. + * + * @param index - element index + * @return element or null + */ + public T get(int index) + { + if(index < mElements.size()) + { + return mElements.get(index); + } - public void setListener( Listener listener ) - { - mListener = listener; - } + return null; + } + + public int getSize() + { + return mSize; + } + + public List getElements() + { + return Collections.unmodifiableList(mElements); + } + + @Override + public void receive(T element) + { + T previous = null; + + if(mElements.size() > mPointer) + { + previous = mElements.get(mPointer); + + mElements.set(mPointer, element); + } + else + { + mElements.add(element); + } + + mPointer++; + + if(mPointer >= mSize) + { + mPointer = 0; + } + + if(previous != null && mListener != null) + { + mListener.receive(previous); + } + } + + public void setListener(Listener listener) + { + mListener = listener; + } } diff --git a/src/main/java/io/github/dsheirer/buffer/FloatCircularBuffer.java b/src/main/java/io/github/dsheirer/buffer/RealCircularBuffer.java similarity index 97% rename from src/main/java/io/github/dsheirer/buffer/FloatCircularBuffer.java rename to src/main/java/io/github/dsheirer/buffer/RealCircularBuffer.java index 855a7e2a..f4274c8a 100644 --- a/src/main/java/io/github/dsheirer/buffer/FloatCircularBuffer.java +++ b/src/main/java/io/github/dsheirer/buffer/RealCircularBuffer.java @@ -24,12 +24,12 @@ package io.github.dsheirer.buffer; * * Can be used as a delay-type buffer, to delay samples by the 'size' amount */ -public class FloatCircularBuffer +public class RealCircularBuffer { float[] mBuffer; int mBufferPointer = 0; - public FloatCircularBuffer(int size) + public RealCircularBuffer(int size) { mBuffer = new float[size]; } 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 dddf3690..bafa24fc 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/FilterFactory.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/FilterFactory.java @@ -727,6 +727,42 @@ public class FilterFactory return taps; } + /** + * Polyphase M2 channelizer sync filter. Designed for an M*channel input and an M2 channel output. + * @param channelBandwidth per channel + * @param channels count + * @param tapsPerChannel minumum. This may be increased to meet the band edge -6.02dB requirement + * @param windowType to use + * @param logResults to log the results of the design + * @return filter + * @throws FilterDesignException if the filter cannot be designed with a band edge of -6.02dB + */ + public static float[] getSincM2Channelizer(double channelBandwidth, int channels, int tapsPerChannel, + Window.WindowType windowType, boolean logResults) throws FilterDesignException + { + return getSincFilter(channelBandwidth * channels * 2.0, channelBandwidth, + channels, tapsPerChannel, windowType,logResults); + } + + /** + * Polyphase M2 synthesizer sync filter. Designed for multiple M2 oversampled channel inputs and + * an M*channel count output. + * + * @param channelBandwidth per channel + * @param channels count + * @param tapsPerChannel minumum. This may be increased to meet the band edge -6.02dB requirement + * @param windowType to use + * @param logResults to log the results of the design + * @return filter + * @throws FilterDesignException if the filter cannot be designed with a band edge of -6.02dB + */ + public static float[] getSincM2Synthesizer(double channelBandwidth, int channels, int tapsPerChannel, + Window.WindowType windowType, boolean logResults) throws FilterDesignException + { + return getSincFilter(channelBandwidth * channels, channelBandwidth, channels, + tapsPerChannel, windowType,logResults); + } + /** * Creates a windowed-sync (Nyquist) M/2 prototype FIR filter for use with a Polyphase Channelizer/Synthesizer. * The filter is designed for x2 oversampling of each channel and the filter cutoff frequency is incrementally @@ -752,14 +788,13 @@ public class FilterFactory * * @throws FilterDesignException if a filter cannot be designed with a band edge attenuation of -6.02 dB */ - public static float[] getSincChannelizer(double channelBandwidth, int channels, int tapsPerChannel, - Window.WindowType windowType, boolean logResults) throws FilterDesignException + public static float[] getSincFilter(double sampleRate, double channelBandwidth, int channels, + int tapsPerChannel, Window.WindowType windowType, boolean logResults) throws FilterDesignException { int currentTapsPerChannel = tapsPerChannel; - int sampleRate = (int)(channelBandwidth * (double)channels * 2.0); int filterLength = (channels * currentTapsPerChannel) - 1; - double cutoffFrequency = channelBandwidth / (double)sampleRate; + double cutoffFrequency = channelBandwidth / sampleRate; double bandEdge = cutoffFrequency; double increment = cutoffFrequency * 0.1; @@ -768,7 +803,7 @@ public class FilterFactory double response = FilterFactory.evaluate(taps, bandEdge); //Set cutoff adjustment threshold - we'll test cutoff frequencies to around 1 hertz resolution - double incrementThreshold = 1.0 / (double)sampleRate; + double incrementThreshold = 1.0 / sampleRate; //Iteratively evaluate filters to find the filter with the highest cutoff frequency that meets the objective while(increment > incrementThreshold) @@ -819,7 +854,7 @@ public class FilterFactory } filterLength = channels * currentTapsPerChannel - 1; - cutoffFrequency = ((double)channelBandwidth / (double)sampleRate); + cutoffFrequency = channelBandwidth / sampleRate; increment = cutoffFrequency * 0.1; } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ChannelCalculator.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ChannelCalculator.java index 8565d3ed..0f387d8b 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ChannelCalculator.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ChannelCalculator.java @@ -396,7 +396,8 @@ public class ChannelCalculator { if(index < 0 || index >= getChannelCount()) { - throw new IllegalArgumentException("Illegal channel index"); + throw new IllegalArgumentException("Illegal channel index [" + index + "] current max channels[" + + getChannelCount() + "]"); } int wrapAroundIndex = getWrapAroundIndex(); @@ -516,18 +517,19 @@ public class ChannelCalculator throw new IllegalArgumentException("Indexes cannot be empty"); } - int middleIndex = indexes.get(indexes.size() / 2); + int centerIndex = (indexes.size() - 1) / 2; - if(indexes.size() % 2 == 0) + int index = indexes.get(centerIndex); + + if(index == getWrapAroundIndex()) { - //Even number of indexes - return (long)getIndexMinimumFrequency(middleIndex, IndexBoundaryPolicy.ADJUST_POSITIVE); + return (long)getIndexCenterFrequency(indexes.get(centerIndex), IndexBoundaryPolicy.ADJUST_NEGATIVE); } else { - //Odd number of indexes - return (long)getIndexCenterFrequency(middleIndex, IndexBoundaryPolicy.ADJUST_POSITIVE); + return (long)getIndexCenterFrequency(indexes.get(centerIndex), IndexBoundaryPolicy.ADJUST_POSITIVE); } + } /** diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ChannelizerViewer.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ChannelizerViewer.java index 2a5cb42d..008e7dc2 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ChannelizerViewer.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ChannelizerViewer.java @@ -201,7 +201,7 @@ public class ChannelizerViewer extends JFrame long baseFrequency = mBaseFrequency + (CHANNEL_BANDWIDTH / 2); - for(int x = 0; x < mChannelCount - 1; x++) + for(int x = 0; x < mChannelCount; x++) { long frequency = baseFrequency + (x * CHANNEL_BANDWIDTH); diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java index 63ec72db..6e62c8c6 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java @@ -86,7 +86,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann { super(sampleRate, (int)(sampleRate / DEFAULT_MINIMUM_CHANNEL_BANDWIDTH)); - float[] filterTaps = FilterFactory.getSincChannelizer(getChannelSampleRate(), getChannelCount(), + float[] filterTaps = FilterFactory.getSincM2Channelizer(getChannelSampleRate(), getChannelCount(), tapsPerFilter, WindowType.BLACKMAN_HARRIS_7, true); double oversampledChannelSampleRate = getChannelSampleRate() * 2.0; @@ -107,7 +107,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann { super.setSampleRate(sampleRate); - float[] filterTaps = FilterFactory.getSincChannelizer(getChannelSampleRate(), getChannelCount(), + float[] filterTaps = FilterFactory.getSincM2Channelizer(getChannelSampleRate(), getChannelCount(), 15, WindowType.BLACKMAN_HARRIS_7, true); init(filterTaps); 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 e0fc7eed..57de3a4e 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 @@ -15,6 +15,8 @@ ******************************************************************************/ package io.github.dsheirer.dsp.filter.channelizer; +import io.github.dsheirer.dsp.filter.FilterFactory; +import io.github.dsheirer.dsp.filter.Window; import io.github.dsheirer.dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor; import io.github.dsheirer.dsp.filter.channelizer.output.OneChannelOutputProcessor; import io.github.dsheirer.dsp.filter.channelizer.output.TwoChannelOutputProcessor; @@ -32,7 +34,9 @@ import io.github.dsheirer.source.tuner.channel.TunerChannelSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Queue; import java.util.SortedSet; import java.util.TreeSet; @@ -54,6 +58,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor private ChannelSourceEventListener mChannelSourceEventListener = new ChannelSourceEventListener(); private BufferSourceEventMonitor mBufferSourceEventMonitor = new BufferSourceEventMonitor(); private ScheduledBufferProcessor mBufferProcessor; + private Map mOutputProcessorFilters = new HashMap<>(); /** * Polyphase Channel Manager is a DDC channel manager and complex buffer queue/processor for a tuner. This class @@ -166,7 +171,15 @@ public class PolyphaseChannelManager implements ISourceEventProcessor case 1: return new OneChannelOutputProcessor(mChannelCalculator.getChannelSampleRate(), indexes); case 2: - return new TwoChannelOutputProcessor(mChannelCalculator.getChannelSampleRate(), indexes); + try + { + float[] filter = getOutputProcessorFilter(2); + return new TwoChannelOutputProcessor(mChannelCalculator.getChannelSampleRate(), indexes, filter); + } + catch(FilterDesignException fde) + { + mLog.error("Error designing 2 channel synthesis filter for output processor"); + } default: //TODO: create output processor for greater than 2 input channels mLog.error("Request to create an output processor for unexpected channel index size:" + indexes.size()); @@ -241,6 +254,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor //Update channel calculator immediately so that channels can be allocated mChannelCalculator.setCenterFrequency(sourceEvent.getValue().longValue()); + mLog.debug("Tuner frequency change: " + sourceEvent.getValue().longValue()); //Defer channelizer configuration changes to be handled on the buffer processor thread mBufferSourceEventMonitor.receive(sourceEvent); break; @@ -294,6 +308,9 @@ public class PolyphaseChannelManager implements ISourceEventProcessor mLog.error("Could not create filter for polyphase channelizer for sample rate [" + sampleRate + "]", fde); } + //Clear the previous channel synthesis filters so they can be recreated for the new channel sample rate + mOutputProcessorFilters.clear(); + //Broadcast the new channel sample rate double updatedChannelSampleRate = mChannelCalculator.getChannelSampleRate(); updateOutputProcessors(SourceEvent.sampleRateChange(updatedChannelSampleRate)); @@ -322,6 +339,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor { try { + mLog.debug("**Sending source event to channel: " + sourceEvent.toString()); channelSource.process(sourceEvent); } catch(SourceException se) @@ -352,11 +370,26 @@ public class PolyphaseChannelManager implements ISourceEventProcessor //If the indexes size is the same then update the current processor, otherwise create a new one if(channelSource.getPolyphaseChannelOutputProcessor().getInputChannelCount() == indexes.size()) { + mLog.debug("Updating existing output processor - indexes:" + indexes + " freq:" + centerFrequency); channelSource.getPolyphaseChannelOutputProcessor().setPolyphaseChannelIndices(indexes); channelSource.setFrequency(centerFrequency); + + if(indexes.size() > 1) + { + try + { + float[] filter = getOutputProcessorFilter(indexes.size()); + channelSource.getPolyphaseChannelOutputProcessor().setSynthesisFilter(filter); + } + catch(FilterDesignException fde) + { + mLog.error("Error creating an updated synthesis filter for the channel output processor"); + } + } } else { + mLog.debug("Setting new output processor - indexes:" + indexes + " freq:" + centerFrequency); channelSource.setPolyphaseChannelOutputProcessor(getOutputProcessor(indexes), centerFrequency); } } @@ -409,6 +442,29 @@ public class PolyphaseChannelManager implements ISourceEventProcessor mSourceEventBroadcaster.removeListener(listener); } + /** + * Generates (or reuses) an output processor filter for the specified number of channels. Each + * filter is created only once and stored in a map for reuse. This map is cleared anytime that the + * input sample rate changes, so that the filters can be recreated with the new channel sample rate. + * @param channels count + * @return filter + * @throws FilterDesignException if the filter cannot be designed to specification (-6 dB band edge) + */ + private float[] getOutputProcessorFilter(int channels) throws FilterDesignException + { + float[] taps = mOutputProcessorFilters.get(channels); + + if(taps == null) + { + taps = FilterFactory.getSincM2Synthesizer(mChannelCalculator.getChannelBandwidth(), channels, + POLYPHASE_FILTER_TAPS_PER_CHANNEL, Window.WindowType.BLACKMAN_HARRIS_7, true); + + mOutputProcessorFilters.put(channels, taps); + } + + return taps; + } + /** * Internal class for handling requests for start/stop sample stream from polyphase channel sources */ @@ -482,7 +538,8 @@ public class PolyphaseChannelManager implements ISourceEventProcessor switch(queuedSourceEvent.getEvent()) { case NOTIFICATION_FREQUENCY_CHANGE: - updateOutputProcessors(queuedSourceEvent); + //Don't send the tuner's frequency change event down to the channels - it would cause chaos + updateOutputProcessors(null); break; case NOTIFICATION_SAMPLE_RATE_CHANGE: updateChannelizerSampleRate(queuedSourceEvent.getValue().intValue()); 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 2b5145ef..3d2bcab4 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 @@ -186,6 +186,7 @@ public class PolyphaseChannelSource extends TunerChannelSource @Override protected void setFrequency(long frequency) { + mLog.debug("Center Frequency set to: " + frequency + " for channel " + getTunerChannel().toString()); mCenterFrequency = frequency; //Set frequency correction to zero to trigger an update to the mixer and allow downstream monitors to diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/SynthesizerViewer.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/SynthesizerViewer.java new file mode 100644 index 00000000..9352c5eb --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/SynthesizerViewer.java @@ -0,0 +1,303 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see + * + ******************************************************************************/ +package io.github.dsheirer.dsp.filter.channelizer; + +import io.github.dsheirer.dsp.filter.FilterFactory; +import io.github.dsheirer.dsp.filter.Window; +import io.github.dsheirer.dsp.filter.design.FilterDesignException; +import io.github.dsheirer.dsp.mixer.IOscillator; +import io.github.dsheirer.dsp.mixer.LowPhaseNoiseOscillator; +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.sample.SampleType; +import io.github.dsheirer.sample.complex.ComplexBuffer; +import io.github.dsheirer.settings.SettingsManager; +import io.github.dsheirer.source.SourceEvent; +import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel; +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 io.github.dsheirer.util.ThreadPool; +import net.miginfocom.swing.MigLayout; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.SpinnerNumberModel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.util.concurrent.TimeUnit; + +public class SynthesizerViewer extends JFrame +{ + private final static Logger mLog = LoggerFactory.getLogger(SynthesizerViewer.class); + + private static final int CHANNEL_BANDWIDTH = 12500; + private static final int CHANNEL_SAMPLE_RATE = 25000; + private static final int CHANNEL_FFT_FRAME_RATE = 20; //frames per second + private static final int DATA_GENERATOR_FRAME_RATE = 50; //frames per second + + private SettingsManager mSettingsManager = new SettingsManager(new TunerConfigurationModel()); + private JPanel mPrimaryPanel; + private PrimarySpectrumPanel mPrimarySpectrumPanel; + private ChannelPanel mChannel1Panel; + private ChannelPanel mChannel2Panel; + private ChannelControlPanel mChannel1ControlPanel; + private ChannelControlPanel mChannel2ControlPanel; + private DFTSize mMainPanelDFTSize = DFTSize.FFT32768; + private DFTSize mChannelPanelDFTSize = DFTSize.FFT08192; + + /** + * GUI Test utility for researching polyphase synthesizers. + */ + public SynthesizerViewer() + { + init(); + } + + public void start() + { + ThreadPool.SCHEDULED.scheduleAtFixedRate(new DataGenerationManager(), 0, 1000 / DATA_GENERATOR_FRAME_RATE, TimeUnit.MILLISECONDS); + } + + private void init() + { + setTitle("Polyphase Synthesizer Viewer"); + setSize(500, 400); + 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][grow,fill]")); + mPrimaryPanel.add(getSpectrumPanel(), "span"); + mPrimaryPanel.add(getChannel1Panel()); + mPrimaryPanel.add(getChannel2Panel()); + } + + return mPrimaryPanel; + } + + private PrimarySpectrumPanel getSpectrumPanel() + { + if(mPrimarySpectrumPanel == null) + { + mPrimarySpectrumPanel = new PrimarySpectrumPanel(mSettingsManager); + mPrimarySpectrumPanel.setPreferredSize(new Dimension(500, 200)); + mPrimarySpectrumPanel.setDFTSize(mMainPanelDFTSize); + } + + return mPrimarySpectrumPanel; + } + + private ChannelPanel getChannel1Panel() + { + if(mChannel1Panel == null) + { + mChannel1Panel = new ChannelPanel(mSettingsManager, getChannel1ControlPanel()); + mChannel1Panel.setPreferredSize(new Dimension(250, 200)); + mChannel1Panel.setDFTSize(mChannelPanelDFTSize); + } + + return mChannel1Panel; + } + + private ChannelPanel getChannel2Panel() + { + if(mChannel2Panel == null) + { + mChannel2Panel = new ChannelPanel(mSettingsManager, getChannel2ControlPanel()); + mChannel2Panel.setPreferredSize(new Dimension(250, 200)); + mChannel2Panel.setDFTSize(mChannelPanelDFTSize); + } + + return mChannel2Panel; + } + + public class PrimarySpectrumPanel extends JPanel implements Listener + { + private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX); + private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter(); + private SpectrumPanel mSpectrumPanel; + + public PrimarySpectrumPanel(SettingsManager settingsManager) + { + setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]")); + mSpectrumPanel = new SpectrumPanel(settingsManager); + mSpectrumPanel.setSampleSize(12); + add(mSpectrumPanel); + + mDFTProcessor.addConverter(mComplexDecibelConverter); + mDFTProcessor.process(SourceEvent.sampleRateChange(CHANNEL_SAMPLE_RATE)); + mDFTProcessor.setFrameRate(CHANNEL_FFT_FRAME_RATE); + mComplexDecibelConverter.addListener(mSpectrumPanel); + } + + public void setDFTSize(DFTSize dftSize) + { + mDFTProcessor.setDFTSize(dftSize); + } + + @Override + public void receive(ComplexBuffer complexBuffer) + { + mDFTProcessor.receive(complexBuffer); + } + } + + public class ChannelPanel extends JPanel implements Listener + { + private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX); + private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter(); + private SpectrumPanel mSpectrumPanel; + + public ChannelPanel(SettingsManager settingsManager, ChannelControlPanel channelControlPanel) + { + setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill][]")); + mSpectrumPanel = new SpectrumPanel(settingsManager); + mSpectrumPanel.setSampleSize(12); + add(mSpectrumPanel, "wrap"); + add(channelControlPanel); + + mDFTProcessor.addConverter(mComplexDecibelConverter); + mDFTProcessor.process(SourceEvent.sampleRateChange(CHANNEL_SAMPLE_RATE)); + mDFTProcessor.setFrameRate(CHANNEL_FFT_FRAME_RATE); + + mComplexDecibelConverter.addListener(mSpectrumPanel); + } + + public void setDFTSize(DFTSize dftSize) + { + mDFTProcessor.setDFTSize(dftSize); + } + + @Override + public void receive(ComplexBuffer complexBuffer) + { + mDFTProcessor.receive(complexBuffer); + } + } + + private ChannelControlPanel getChannel1ControlPanel() + { + if(mChannel1ControlPanel == null) + { + mChannel1ControlPanel = new ChannelControlPanel(); + } + + return mChannel1ControlPanel; + } + + private ChannelControlPanel getChannel2ControlPanel() + { + if(mChannel2ControlPanel == null) + { + mChannel2ControlPanel = new ChannelControlPanel(); + } + + return mChannel2ControlPanel; + } + + public class ChannelControlPanel extends JPanel + { + private static final int SAMPLE_RATE = 25000; + private static final int MIN_FREQUENCY = -6250; + private static final int MAX_FREQUENCY = 6250; + private static final int DEFAULT_FREQUENCY = 50; + + private IOscillator mOscillator = new LowPhaseNoiseOscillator(6000, CHANNEL_SAMPLE_RATE); + + public ChannelControlPanel() + { + setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]")); + add(new JLabel("Tone:"), "align right"); + + SpinnerNumberModel model = new SpinnerNumberModel(DEFAULT_FREQUENCY, MIN_FREQUENCY, MAX_FREQUENCY, 100); + model.addChangeListener(new ChangeListener() + { + @Override + public void stateChanged(ChangeEvent e) + { + long toneFrequency = model.getNumber().longValue(); + mOscillator.setFrequency(toneFrequency); + } + }); + + JSpinner spinner = new JSpinner(model); + add(spinner); + add(new JLabel("Hz")); + } + + public IOscillator getOscillator() + { + return mOscillator; + } + } + + public class DataGenerationManager implements Runnable + { + private TwoChannelSynthesizerM2 mSynthesizer; + private int mSamplesPerCycle = 25000 / DATA_GENERATOR_FRAME_RATE; + + public DataGenerationManager() + { + try + { + float[] taps = FilterFactory.getSincM2Synthesizer(12500.0, 2, 19, + Window.WindowType.BLACKMAN_HARRIS_7, true); + mSynthesizer = new TwoChannelSynthesizerM2(taps); + } + catch(FilterDesignException fde) + { + mLog.error("Filter design error", fde); + } + } + + @Override + public void run() + { + float[] channel1 = getChannel1ControlPanel().getOscillator().generateComplex(mSamplesPerCycle); + float[] channel2 = getChannel2ControlPanel().getOscillator().generateComplex(mSamplesPerCycle); + + float[] synthesized = mSynthesizer.process(channel1, channel2); + + getChannel1Panel().receive(new ComplexBuffer(channel1)); + getChannel2Panel().receive(new ComplexBuffer(channel2)); + getSpectrumPanel().receive(new ComplexBuffer(synthesized)); + } + } + + public static void main(String[] args) + { + final SynthesizerViewer frame = new SynthesizerViewer(); + + EventQueue.invokeLater(() -> { + frame.setVisible(true); + frame.start(); + }); + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..093f682b --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/TwoChannelSynthesizerM2.java @@ -0,0 +1,172 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see + * + ******************************************************************************/ +package io.github.dsheirer.dsp.filter.channelizer; + +import io.github.dsheirer.buffer.RealCircularBuffer; +import io.github.dsheirer.dsp.filter.FilterFactory; +import io.github.dsheirer.dsp.filter.Window; +import io.github.dsheirer.dsp.filter.design.FilterDesignException; +import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter; +import io.github.dsheirer.dsp.mixer.IOscillator; +import io.github.dsheirer.dsp.mixer.LowPhaseNoiseOscillator; +import org.jtransforms.fft.FloatFFT_1D; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; + +public class TwoChannelSynthesizerM2 +{ + private final static Logger mLog = LoggerFactory.getLogger(TwoChannelSynthesizerM2.class); + + private ComplexFIRFilter mFilter1; + private ComplexFIRFilter mFilter2; + + private FloatFFT_1D mFFT = new FloatFFT_1D(2); + private boolean mFlag = false; + + /** + * Polyphase synthesizer for mixing two M2 oversampled channels into a composite channel using + * perfect reconstruction filters (-6db at band edge). Output sample rate is the same as the + * input sample rate of one of the channels. + */ + public TwoChannelSynthesizerM2(float[] filter) + { + init(filter); + } + + /** + * Partitions the filter into 2 polyphase partitions and uses only the first filter. The second + * filter partition and all corresponding samples represent the oversampling excess that will be + * thrown away. + * + * We create two instances of filter 1 to simplify buffer management where we have to swap the data + * buffer at each sample to correct for phase shift introduced by buffer swapping when down sampling + * from the M2 sample rates of each channel. + * + * @param filter to partition into 2 polyphase filters. + */ + private void init(float[] filter) + { + int length = (filter.length / 2) + (filter.length % 2); + + float[] filter1 = new float[length]; + float[] filter2 = new float[length]; + + for(int x = 0; x < filter.length; x += 2) + { + //Load the filter in reverse to facilitate convolution + int index = length - (x / 2) - 1; + + filter1[index] = filter[x]; + + if(x + 1 < filter.length) + { + filter2[index] = filter[x + 1]; + } + } + + mFilter1 = new ComplexFIRFilter(filter1, 1.0f); + mFilter2 = new ComplexFIRFilter(filter2, 1.0f); + } + + /** + * Synthesizes a new channel from the channel 1 and channel 2 sample arrays that are arranged as + * i0, q0, i1, q1 ... iN-1, qN-1 + * + * @param channel1 input channel + * @param channel2 input channel + * @return synthesized channel results of the same length as both channel 1/2 input arrays. + */ + public float[] process(float[] channel1, float[] channel2) + { + if(channel1.length != channel2.length) + { + throw new IllegalArgumentException("Channel 1 and 2 array length must be equal"); + } + + float[] output = new float[channel1.length]; + + float[] buffer = new float[4]; + + for(int x = 0; x < channel1.length; x += 2) + { + buffer[0] = channel1[x]; //i + buffer[1] = channel1[x + 1]; //q + buffer[2] = channel2[x]; //i + buffer[3] = channel2[x + 1]; //q + + mFFT.complexInverse(buffer, true); + + if(mFlag) + { + output[x] = mFilter1.filterInphase(buffer[0]) + mFilter2.filterInphase(buffer[2]); + output[x + 1] = mFilter1.filterQuadrature(buffer[1]) + mFilter2.filterQuadrature(buffer[3]); + } + else + { + output[x] = mFilter1.filterInphase(buffer[2]) + mFilter2.filterInphase(buffer[0]); + output[x + 1] = mFilter1.filterQuadrature(buffer[3]) + mFilter2.filterQuadrature(buffer[1]); + } + + mFlag = !mFlag; + } + + return output; + } + + private static float filter(float[] taps, RealCircularBuffer dataBlock) + { + float accumulator = 0; + + for(int x = 0; x < taps.length; x++) + { + accumulator += (taps[x] * dataBlock.get(x)); + } + + return accumulator; + } + + public static void main(String[] args) + { + mLog.debug("Starting"); + + int sampleCount = 10; + + try + { + float[] taps = FilterFactory.getSincM2Channelizer(12500.0, 2, 19, Window.WindowType.BLACKMAN_HARRIS_7, true); + + TwoChannelSynthesizerM2 synthesizer = new TwoChannelSynthesizerM2(taps); + + IOscillator oscillator = new LowPhaseNoiseOscillator(3000.0, 25000.0); + + float[] channel1 = oscillator.generateComplex(sampleCount); + float[] channel2 = new float[sampleCount * 2]; + + float[] synthesized = synthesizer.process(channel1, channel2); + + mLog.debug("1:" + Arrays.toString(channel1)); + mLog.debug("X:" + Arrays.toString(synthesized)); + } + catch(FilterDesignException fde) + { + mLog.error("Filter design error", fde); + } + + mLog.debug("Finished!!"); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/ChannelOutputProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/ChannelOutputProcessor.java index b699edae..4afb805d 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/ChannelOutputProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/ChannelOutputProcessor.java @@ -19,6 +19,7 @@ package io.github.dsheirer.dsp.filter.channelizer.output; import io.github.dsheirer.dsp.filter.channelizer.PolyphaseChannelResultsBuffer; +import io.github.dsheirer.dsp.mixer.IOscillator; import io.github.dsheirer.dsp.mixer.Oscillator; import io.github.dsheirer.sample.OverflowableTransferQueue; import io.github.dsheirer.sample.complex.TimestampedBufferAssembler; @@ -40,7 +41,7 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP private int mInputChannelCount; //TODO: swap this out and use the LowPhaseNoiseOscillator - private Oscillator mFrequencyCorrectionMixer; + private IOscillator mFrequencyCorrectionMixer; private boolean mFrequencyCorrectionEnabled; /** @@ -74,7 +75,7 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP /** * Oscillator/mixer to use for frequency correction against incominb sample stream */ - protected Oscillator getFrequencyCorrectionMixer() + protected IOscillator getFrequencyCorrectionMixer() { return mFrequencyCorrectionMixer; } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/IPolyphaseChannelOutputProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/IPolyphaseChannelOutputProcessor.java index fe71eba8..916ae095 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/IPolyphaseChannelOutputProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/IPolyphaseChannelOutputProcessor.java @@ -59,6 +59,12 @@ public interface IPolyphaseChannelOutputProcessor */ void setPolyphaseChannelIndices(List indexes); + /** + * Updates the synthesis filter taps for this output processor + * @param filter + */ + void setSynthesisFilter(float[] filter); + /** * Sets the listener to receive buffer overflow notifications * @param source to receive overflow notifications diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelOutputProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelOutputProcessor.java index eaff4feb..e6f52d3f 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelOutputProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelOutputProcessor.java @@ -44,6 +44,12 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor setPolyphaseChannelIndices(channelIndexes); } + @Override + public void setSynthesisFilter(float[] filter) + { + throw new IllegalArgumentException("The one channel output processor does not support filter updates"); + } + /** * Updates this processor to extract a single, specified channel index. * @param indexes containing a single channel index value. 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 3e904d4d..cc58584b 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 @@ -1,24 +1,24 @@ /******************************************************************************* - * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * 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 free software: you can redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see + * You should have received a copy of the GNU General Public License along with this program. + * If not, see * ******************************************************************************/ package io.github.dsheirer.dsp.filter.channelizer.output; +import io.github.dsheirer.dsp.filter.Filters; import io.github.dsheirer.dsp.filter.channelizer.PolyphaseChannelResultsBuffer; +import io.github.dsheirer.dsp.filter.channelizer.TwoChannelSynthesizerM2; +import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter; import io.github.dsheirer.sample.complex.TimestampedBufferAssembler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,6 +27,10 @@ import java.util.List; public class TwoChannelOutputProcessor extends ChannelOutputProcessor { + private ComplexFIRFilter mLowPassFilter = + new ComplexFIRFilter(Filters.HALF_BAND_FILTER_47T.getCoefficients(), 1.0f); + private TwoChannelSynthesizerM2 mSynthesizer; + private final static Logger mLog = LoggerFactory.getLogger(TwoChannelOutputProcessor.class); private int mChannelOffset1; @@ -39,12 +43,31 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor * @param sampleRate of the output sample stream. * @param channelIndexes containing two channel indices. */ - public TwoChannelOutputProcessor(double sampleRate, List channelIndexes) + public TwoChannelOutputProcessor(double sampleRate, List channelIndexes, float[] filter) { //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); setPolyphaseChannelIndices(channelIndexes); + setSynthesisFilter(filter); + } + + /** + * Sets the frequency offset to apply to the incoming samples to mix the desired signal to baseband. + * @param frequencyOffset in hertz + */ + @Override + public void setFrequencyOffset(long frequencyOffset) + { +// super.setFrequencyOffset(frequencyOffset); + mLog.debug("**Ignoring** Setting frequency offset [" + frequencyOffset + "] for channel offsets [" + mChannelOffset1 + "," + mChannelOffset2 + "]"); + } + + + @Override + public void setSynthesisFilter(float[] filter) + { + mSynthesizer = new TwoChannelSynthesizerM2(filter); } /** @@ -78,6 +101,22 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor public void process(List channelResultsBuffers, TimestampedBufferAssembler timestampedBufferAssembler) { - mLog.debug("I got channel results - doing nothing !!"); + for(PolyphaseChannelResultsBuffer buffer: channelResultsBuffers) + { + float[] channel1 = buffer.getChannel(mChannelOffset1); + float[] channel2 = buffer.getChannel(mChannelOffset2); + + //Join the two channels using the synthesizer + float[] synthesized = mSynthesizer.process(channel1, channel2); + + //Apply frequency correction to center the signal of interest within the synthesized channel + getFrequencyCorrectionMixer().mixComplex(synthesized); + + //Apply low-pass filtering to attenuate neighboring channel(s) +// synthesized = mLowPassFilter.filter(synthesized); + + //Send it on its merry way ... + timestampedBufferAssembler.receive(synthesized); + } } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/design/FilterView.java b/src/main/java/io/github/dsheirer/dsp/filter/design/FilterView.java index edaa4289..f0ee3b78 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/design/FilterView.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/design/FilterView.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see + * + ******************************************************************************/ package io.github.dsheirer.dsp.filter.design; import io.github.dsheirer.gui.control.CurveFittedAreaChart; @@ -12,155 +27,163 @@ import org.jtransforms.fft.FloatFFT_1D; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.net.URL; + public class FilterView extends BorderPane { - private final static Logger mLog = LoggerFactory.getLogger( FilterView.class ); + private final static Logger mLog = LoggerFactory.getLogger(FilterView.class); - private CurveFittedAreaChart mImpulseChart; - private NumberAxis mImpulseXAxis; - private NumberAxis mImpulseYAxis; - private CurveFittedAreaChart mDFTChart; - private NumberAxis mDFTXAxis; - private NumberAxis mDFTYAxis; - private static final int FFT_SIZE = 4096; - private FloatFFT_1D mFFT = new FloatFFT_1D( FFT_SIZE ); - - public FilterView( float[] taps ) - { - init(); + private CurveFittedAreaChart mImpulseChart; + private NumberAxis mImpulseXAxis; + private NumberAxis mImpulseYAxis; + private CurveFittedAreaChart mDFTChart; + private NumberAxis mDFTXAxis; + private NumberAxis mDFTYAxis; + private static final int FFT_SIZE = 4096; + private FloatFFT_1D mFFT = new FloatFFT_1D(FFT_SIZE); - if( taps != null ) - { - setImpulseTaps( taps ); + public FilterView(float[] taps) + { + init(); - float[] dft = new float[ FFT_SIZE ]; - System.arraycopy( taps, 0, dft, 0, taps.length ); - - mFFT.realForward( dft ); - - setDFTTaps( convertDFTToDecibel( dft, taps.length ) ); - } - } - - private void init() - { - HBox hbox = new HBox(); - - mImpulseXAxis = new NumberAxis( 0.0, 0.5, 1.0 ); - mImpulseYAxis = new NumberAxis( -0.2, 1.2, .1 ); - - mImpulseChart = new CurveFittedAreaChart( mImpulseXAxis, mImpulseYAxis ); - mImpulseChart.setLegendVisible( false ); - mImpulseChart.setHorizontalGridLinesVisible( false ); - mImpulseChart.setVerticalGridLinesVisible(false); + if(taps != null) + { + setImpulseTaps(taps); + + float[] dft = new float[FFT_SIZE]; + System.arraycopy(taps, 0, dft, 0, taps.length); + + mFFT.realForward(dft); + + setDFTTaps(convertDFTToDecibel(dft, taps.length)); + } + } + + private void init() + { + HBox hbox = new HBox(); + + mImpulseXAxis = new NumberAxis(0.0, 0.5, 1.0); + mImpulseYAxis = new NumberAxis(-0.2, 1.2, .1); + + mImpulseChart = new CurveFittedAreaChart(mImpulseXAxis, mImpulseYAxis); + mImpulseChart.setLegendVisible(false); + mImpulseChart.setHorizontalGridLinesVisible(false); + mImpulseChart.setVerticalGridLinesVisible(false); mImpulseChart.setAlternativeColumnFillVisible(false); mImpulseChart.setAlternativeRowFillVisible(false); - - String css = FilterView.class.getResource( "FilterView.css" ).toExternalForm(); - mImpulseChart.getStylesheets().add( css ); - hbox.getChildren().add( mImpulseChart ); - HBox.setHgrow( mImpulseChart, Priority.ALWAYS ); - - mDFTXAxis = new NumberAxis( 0.0, FFT_SIZE / 2, FFT_SIZE / 8 ); - mDFTYAxis = new NumberAxis( -200.0, 10.0, 10.0 ); - - mDFTChart = new CurveFittedAreaChart( mDFTXAxis, mDFTYAxis ); - mDFTChart.setLegendVisible( false ); - mDFTChart.setHorizontalGridLinesVisible( false ); - mDFTChart.setVerticalGridLinesVisible(false); + hbox.getChildren().add(mImpulseChart); + HBox.setHgrow(mImpulseChart, Priority.ALWAYS); + + mDFTXAxis = new NumberAxis(0.0, FFT_SIZE / 2, FFT_SIZE / 8); + mDFTYAxis = new NumberAxis(-200.0, 10.0, 10.0); + + mDFTChart = new CurveFittedAreaChart(mDFTXAxis, mDFTYAxis); + mDFTChart.setLegendVisible(false); + mDFTChart.setHorizontalGridLinesVisible(false); + mDFTChart.setVerticalGridLinesVisible(false); mDFTChart.setAlternativeColumnFillVisible(false); mDFTChart.setAlternativeRowFillVisible(false); - mDFTChart.getStylesheets().add( css ); - - hbox.getChildren().add( mDFTChart ); - HBox.setHgrow( mDFTChart, Priority.ALWAYS ); - setCenter( hbox ); - } - - public void setImpulseTaps( double[] taps ) - { - float[] convertedTaps = new float[ taps.length ]; - - for( int x = 0; x < taps.length; x++ ) - { - convertedTaps[ x ] = (float)taps[ x ]; - } - - setImpulseTaps( convertedTaps ); - } - - public void setImpulseTaps( float[] taps ) - { - double smallest = 0.0; - double largest = 0.0; - - final XYChart.Series series = new XYChart.Series<>(); - - for( int x = 0; x < taps.length; x++ ) + + URL url = getClass().getResource("/../resources/FilterView.css"); + + if(url != null) { - if( taps[ x ] < smallest ) - { - smallest = taps[ x ]; - } - - if( taps[ x ] > largest ) - { - largest = taps[ x ]; - } - - series.getData().add( new XYChart.Data( x, taps[ x ] ) ); + String css = url.toExternalForm(); + mImpulseChart.getStylesheets().add(css); + mDFTChart.getStylesheets().add(css); } - - mImpulseXAxis.setLowerBound( 0.0 ); - mImpulseXAxis.setUpperBound( taps.length - 1 ); - mImpulseXAxis.setTickUnit( 1.0 ); - - double padding = Math.abs( largest - smallest ) * .1; - - mImpulseYAxis.setLowerBound( smallest - padding ); - mImpulseYAxis.setUpperBound( largest + padding ); - mImpulseYAxis.setTickUnit( padding ); + + hbox.getChildren().add(mDFTChart); + HBox.setHgrow(mDFTChart, Priority.ALWAYS); + + setCenter(hbox); + } + + public void setImpulseTaps(double[] taps) + { + float[] convertedTaps = new float[taps.length]; + + for(int x = 0; x < taps.length; x++) + { + convertedTaps[x] = (float)taps[x]; + } + + setImpulseTaps(convertedTaps); + } + + public void setImpulseTaps(float[] taps) + { + double smallest = 0.0; + double largest = 0.0; + + final XYChart.Series series = new XYChart.Series<>(); + + for(int x = 0; x < taps.length; x++) + { + if(taps[x] < smallest) + { + smallest = taps[x]; + } + + if(taps[x] > largest) + { + largest = taps[x]; + } + + series.getData().add(new XYChart.Data(x, taps[x])); + } + + mImpulseXAxis.setLowerBound(0.0); + mImpulseXAxis.setUpperBound(taps.length - 1); + mImpulseXAxis.setTickUnit(1.0); + + double padding = Math.abs(largest - smallest) * .1; + + mImpulseYAxis.setLowerBound(smallest - padding); + mImpulseYAxis.setUpperBound(largest + padding); + mImpulseYAxis.setTickUnit(padding); ObservableList> list = mImpulseChart.getData(); - list.add( series ); - } - - public void setDFTTaps( float[] taps ) - { - double smallest = 0.0; - - final XYChart.Series series = new XYChart.Series<>(); - - for( int x = 0; x < taps.length; x++ ) - { - if( taps[ x ] < smallest ) - { - smallest = taps[ x ]; - } - - series.getData().add( new XYChart.Data( x, taps[ x ] ) ); - } - - ObservableList> list = mDFTChart.getData(); - list.add( series ); - } - - public float[] convertDFTToDecibel( float[] dft, int tapLength ) + list.add(series); + } + + public void setDFTTaps(float[] taps) { - float[] decibels = new float[ dft.length / 2 ]; + double smallest = 0.0; - int index = 0; - - for( int x = 0; x < decibels.length; x ++ ) - { - index = x * 2; - - decibels[ x ] = 20.0f * (float)Math.log10( ( ( dft[ index ] * dft[ index ] ) + - ( dft[ index + 1 ] * dft[ index + 1 ] ) ) ); - } + final XYChart.Series series = new XYChart.Series<>(); - return decibels; + for(int x = 0; x < taps.length; x++) + { + if(taps[x] < smallest) + { + smallest = taps[x]; + } + + series.getData().add(new XYChart.Data(x, taps[x])); + } + + ObservableList> list = mDFTChart.getData(); + list.add(series); + } + + public float[] convertDFTToDecibel(float[] dft, int tapLength) + { + float[] decibels = new float[dft.length / 2]; + + int index = 0; + + for(int x = 0; x < decibels.length; x++) + { + index = x * 2; + + decibels[x] = 20.0f * (float)Math.log10(((dft[index] * dft[index]) + + (dft[index + 1] * dft[index + 1]))); + } + + return decibels; } } 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 e2e7d9ca..b7e26b4a 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; @@ -72,23 +73,29 @@ public class FilterViewer extends Application float[] taps = null; -// taps = FilterFactory.getLowPass(48000, 6750, 7500, 60, -// Window.WindowType.HAMMING, true); - try { - RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification); - - if(designer.isValid()) - { - taps = designer.getImpulseResponse(); - } + taps = FilterFactory.getSincM2Channelizer(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 8190b9a1..11e6955e 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 @@ -1,3 +1,18 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see + * + ******************************************************************************/ package io.github.dsheirer.dsp.filter.fir.complex; import io.github.dsheirer.dsp.filter.fir.FIRFilter; @@ -6,49 +21,105 @@ import io.github.dsheirer.sample.complex.Complex; public class ComplexFIRFilter extends FIRFilter { - private RealFIRFilter mIFilter; - private RealFIRFilter mQFilter; - - /** - * Complex FIR Filter for processing complex sample pairs. Wraps two real - * FIR filters for processing each of the inphase and quadrature samples. - * - * @param coefficients - filter taps - * @param gain - gain to apply to filtered outputs - use 1.0f for no gain - */ - public ComplexFIRFilter( float[] coefficients, float gain ) - { - mIFilter = new RealFIRFilter( coefficients, gain ); - mQFilter = new RealFIRFilter( coefficients, gain ); - } - - public float[] getCoefficients() - { - return mIFilter.getCoefficients(); - } - - public float filterInphase( float sample ) - { - return mIFilter.filter( sample ); - } - - public float filterQuadrature( float sample ) - { - return mQFilter.filter( sample ); - } - - public Complex filter( Complex sample ) - { - float i = filterInphase( sample.inphase() ); - float q = filterQuadrature( sample.quadrature() ); - - return new Complex( i, q ); - } - - @Override - public void dispose() - { - mIFilter.dispose(); - mQFilter.dispose(); - } + private RealFIRFilter mIFilter; + private RealFIRFilter mQFilter; + + /** + * Complex FIR Filter for processing complex sample pairs. Wraps two real + * FIR filters for processing each of the inphase and quadrature samples. + * + * @param coefficients - filter taps + * @param gain - gain to apply to filtered outputs - use 1.0f for no gain + */ + public ComplexFIRFilter(float[] coefficients, float gain) + { + mIFilter = new RealFIRFilter(coefficients, gain); + mQFilter = new RealFIRFilter(coefficients, gain); + } + + /** + * Coefficients used in each of the I and Q filters + */ + public float[] getCoefficients() + { + return mIFilter.getCoefficients(); + } + + /** + * Filters the inphase sample independent of the quadrature side of the filter + * @param sample to filter + * @return filtered inphase sample + */ + public float filterInphase(float sample) + { + return mIFilter.filter(sample); + } + + /** + * Filters the quadrature sample independent of the inphase side of the filter + * @param sample to filter + * @return filtered quadrature sample + */ + public float filterQuadrature(float sample) + { + return mQFilter.filter(sample); + } + + /** + * Loads the complex sample and provides a filtered output + * @param sample to load + * @return filtered complex sample + */ + public Complex filter(Complex sample) + { + return filter(sample.inphase(), sample.quadrature()); + } + + /** + * Loads the complex sample and provides a filtered output + * @param i inphase sample to load + * @param q quadrature sample to load + * @return filtered complex sample + */ + public Complex filter(float i, float q) + { + float filteredI = filterInphase(i); + float filteredQ = filterQuadrature(q); + + return new Complex(filteredI, filteredQ); + } + + /** + * Filters the complex sample array where the samples are loaded as i0,q0,i1,q1 ... iN-2,qN-1 + * + * @param samples to filter. + */ + public float[] filter(float[] samples) + { + for(int x = 0; x < samples.length; x += 2) + { + samples[x] = filterInphase(samples[x]); + samples[x + 1] = filterInphase(samples[x + 1]); + } + + return samples; + } + + /** + * Loads the complex sample into the filter but does not calculate a filtered output sample + * @param i inphase sample + * @param q quadrature sample + */ + public void load(float i, float q) + { + mIFilter.load(i); + mQFilter.load(q); + } + + @Override + public void dispose() + { + mIFilter.dispose(); + mQFilter.dispose(); + } } 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 d582dfa1..ec019bdc 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 @@ -1,19 +1,17 @@ /******************************************************************************* - * SDR Trunk - * Copyright (C) 2015 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see + * sdr-trunk + * Copyright (C) 2014-2018 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see + * ******************************************************************************/ package io.github.dsheirer.dsp.filter.fir.real; @@ -21,85 +19,106 @@ import io.github.dsheirer.dsp.filter.fir.FIRFilter; public class RealFIRFilter extends FIRFilter { - private float[] mBuffer; + private float[] mBuffer; private int mBufferSize = 1; - private int mBufferPointer = 0; - private int[][] mIndexMap; - - private float[] mCoefficients; - private float mGain; - - /** - * Float sample FIR filter base class. - * - * @param coefficients - filter coefficients - * @param gain value to apply to the filtered output - use 1.0f for no gain - */ - public RealFIRFilter( float[] coefficients, float gain ) - { - mCoefficients = coefficients; - mGain = gain; + private int mBufferPointer = 0; + private int[][] mIndexMap; - mBufferSize = mCoefficients.length; + private float[] mCoefficients; + private float mGain; - mBuffer = new float[ mBufferSize ]; - mBufferPointer = mBufferSize - 1; + /** + * Float sample FIR filter base class. + * + * @param coefficients - filter coefficients + * @param gain value to apply to the filtered output - use 1.0f for no gain + */ + public RealFIRFilter(float[] coefficients, float gain) + { + mCoefficients = coefficients; + mGain = gain; - generateIndexMap( mBufferSize ); - } - - public float[] getCoefficients() - { - return mCoefficients; - } - - @Override - public void dispose() - { - mCoefficients = null; - mIndexMap = null; - mBuffer = null; - } + mBufferSize = mCoefficients.length; - public float filter( float sample ) - { - mBuffer[ mBufferPointer ] = sample; - - float accumulator = 0.0f; + mBuffer = new float[mBufferSize]; + mBufferPointer = mBufferSize - 1; - for( int x = 0; x < mBufferSize; x++ ) - { - accumulator += mCoefficients[ x ] * - mBuffer[ mIndexMap[ mBufferPointer][ x ] ]; - } + generateIndexMap(mBufferSize); + } - mBufferPointer--; + public float[] getCoefficients() + { + return mCoefficients; + } - if( mBufferPointer < 0 ) - { - mBufferPointer += mBufferSize; - } + @Override + public void dispose() + { + mCoefficients = null; + mIndexMap = null; + mBuffer = null; + } - /* Apply gain and return the filtered value */ - return accumulator * mGain; - } + /** + * Loads the sample into the circular buffer without calculating a filtered value. + * @param sample to load + */ + public void load(float sample) + { + mBuffer[mBufferPointer] = sample; - /** - * Generates a circular buffer index map to support lookup of the translated - * index based on the current buffer pointer and the desired sample index. - */ - private void generateIndexMap( int size ) - { - mIndexMap = new int[ size ][ size ]; - - for( int x = 0; x < size; x++ ) - { - for( int y = 0; y < size; y++ ) - { - int z = x + y; - - mIndexMap[ x ][ y ] = z < size ? z : z - size; - } - } - } + mBufferPointer--; + + if(mBufferPointer < 0) + { + mBufferPointer += mBufferSize; + } + } + + /** + * Loads the sample into the circular buffer and calculates a filtered sample output. + * @param sample to load + * @return filtered value + */ + public float filter(float sample) + { + mBuffer[mBufferPointer] = sample; + + float accumulator = 0.0f; + + for(int x = 0; x < mBufferSize; x++) + { + accumulator += mCoefficients[x] * + mBuffer[mIndexMap[mBufferPointer][x]]; + } + + mBufferPointer--; + + if(mBufferPointer < 0) + { + mBufferPointer += mBufferSize; + } + + /* Apply gain and return the filtered value */ + return accumulator * mGain; + } + + /** + * Generates a circular buffer index map to support lookup of the translated + * index based on the current buffer pointer and the desired sample index. + */ + private void generateIndexMap(int size) + { + mIndexMap = new int[size][size]; + + for(int x = 0; x < size; x++) + { + for(int y = 0; y < size; y++) + { + int z = x + y; + + mIndexMap[x][y] = z < size ? z : z - size; + } + } + } } \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/polyphase/PolyphaseFIRDecimatingFilter_RB.java b/src/main/java/io/github/dsheirer/dsp/filter/polyphase/PolyphaseFIRDecimatingFilter_RB.java index 0012b2c9..34a12cb7 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/polyphase/PolyphaseFIRDecimatingFilter_RB.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/polyphase/PolyphaseFIRDecimatingFilter_RB.java @@ -21,6 +21,7 @@ import io.github.dsheirer.dsp.filter.design.FilterDesignException; import io.github.dsheirer.dsp.filter.fir.FIRFilter; import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification; import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner; +import io.github.dsheirer.dsp.mixer.IOscillator; import io.github.dsheirer.dsp.mixer.Oscillator; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.real.RealBuffer; @@ -190,15 +191,9 @@ public class PolyphaseFIRDecimatingFilter_RB extends FIRFilter implements Listen } }); - Oscillator oscillator = new Oscillator(3400, 48000); - - float[] samples = new float[500]; - for(int x = 0; x < 500; x++) - { - samples[x] = oscillator.getFloat(); - oscillator.rotate(); - } + IOscillator oscillator = new Oscillator(3400, 48000); + float[] samples = oscillator.generateReal(500); RealBuffer buffer = new RealBuffer(samples); filter.receive(buffer); } diff --git a/src/main/java/io/github/dsheirer/dsp/gain/AutomaticGainControl.java b/src/main/java/io/github/dsheirer/dsp/gain/AutomaticGainControl.java index 77e4d7f7..4d2ba36d 100644 --- a/src/main/java/io/github/dsheirer/dsp/gain/AutomaticGainControl.java +++ b/src/main/java/io/github/dsheirer/dsp/gain/AutomaticGainControl.java @@ -1,7 +1,7 @@ package io.github.dsheirer.dsp.gain; import io.github.dsheirer.buffer.DoubleCircularBuffer; -import io.github.dsheirer.buffer.FloatCircularBuffer; +import io.github.dsheirer.buffer.RealCircularBuffer; import java.util.concurrent.atomic.AtomicBoolean; @@ -131,8 +131,8 @@ public class AutomaticGainControl private double mAttackAverage = 0.0; private double mDecayAverage = 0.0; - private FloatCircularBuffer mDelayBuffer = - new FloatCircularBuffer( (int)( SAMPLE_RATE * DELAY_TIME_CONSTANT ) ); + private RealCircularBuffer mDelayBuffer = + new RealCircularBuffer( (int)( SAMPLE_RATE * DELAY_TIME_CONSTANT ) ); private DoubleCircularBuffer mMagnitudeBuffer = new DoubleCircularBuffer( (int)( SAMPLE_RATE * WINDOW_TIME_CONSTANT ) ); diff --git a/src/main/java/io/github/dsheirer/dsp/gain/ComplexFeedForwardGainControl.java b/src/main/java/io/github/dsheirer/dsp/gain/ComplexFeedForwardGainControl.java index d1ca98a1..4334e214 100644 --- a/src/main/java/io/github/dsheirer/dsp/gain/ComplexFeedForwardGainControl.java +++ b/src/main/java/io/github/dsheirer/dsp/gain/ComplexFeedForwardGainControl.java @@ -1,6 +1,6 @@ package io.github.dsheirer.dsp.gain; -import io.github.dsheirer.buffer.FloatCircularBuffer; +import io.github.dsheirer.buffer.RealCircularBuffer; import io.github.dsheirer.sample.complex.Complex; import io.github.dsheirer.sample.complex.ComplexSampleListener; @@ -34,7 +34,7 @@ public class ComplexFeedForwardGainControl implements ComplexSampleListener private ComplexSampleListener mListener; - private FloatCircularBuffer mEnvelopeHistory; + private RealCircularBuffer mEnvelopeHistory; private float mMaxEnvelope = 0.0f; private float mGain = 1.0f; @@ -53,7 +53,7 @@ public class ComplexFeedForwardGainControl implements ComplexSampleListener */ public ComplexFeedForwardGainControl( int window ) { - mEnvelopeHistory = new FloatCircularBuffer( window ); + mEnvelopeHistory = new RealCircularBuffer( window ); } public void dispose() diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/AbstractOscillator.java b/src/main/java/io/github/dsheirer/dsp/mixer/AbstractOscillator.java new file mode 100644 index 00000000..6969ad78 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/mixer/AbstractOscillator.java @@ -0,0 +1,143 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see + * + ******************************************************************************/ +package io.github.dsheirer.dsp.mixer; + +import io.github.dsheirer.sample.complex.Complex; + +public abstract class AbstractOscillator implements IOscillator +{ + private boolean mEnabled; + private double mFrequency; + private double mSampleRate; + + public AbstractOscillator(double frequency, double sampleRate) + { + mFrequency = frequency; + mSampleRate = sampleRate; + mEnabled = frequency != 0; + update(); + } + + /** + * Frequency of the tone being generated by this oscillator + */ + public double getFrequency() + { + return mFrequency; + } + + /** + * Sets or changes the frequency of this oscillator + */ + public void setFrequency(double frequency) + { + mEnabled = frequency != 0; + mFrequency = frequency; + update(); + } + + /** + * Sample rate of this oscillator + */ + public double getSampleRate() + { + return mSampleRate; + } + + /** + * Sets or changes the sample rate of this oscillator + */ + public void setSampleRate(double sampleRate) + { + mSampleRate = sampleRate; + update(); + } + + /** + * Indicates if this oscillator has a non-zero frequency setting. When used as a frequency translating + * mixer, setting a frequency of zero indicates that no frequency correction is needed, so this flag can + * indicate when to apply the oscillator to frequency correct samples. + * + * @return true if the current frequency value is non-zero. + */ + @Override + public boolean isEnabled() + { + return mEnabled; + } + + protected abstract void update(); + + /** + * Performs complex heterodyne against the samples using this oscillator + * @param samples to mix with this oscillator + */ + @Override + public float[] mixComplex(float[] samples) + { + for(int x = 0; x < samples.length; x += 2) + { + float i = Complex.multiplyInphase(samples[x], samples[x + 1], inphase(), quadrature()); + float q = Complex.multiplyQuadrature(samples[x], samples[x + 1], inphase(), quadrature()); + + samples[x] = i; + samples[x + 1] = q; + + rotate(); + } + + return samples; + } + + /** + * Generates an array of real samples from this oscillator. + * @param sampleCount number of samples to generate and length of the resulting float array. + * @return + */ + @Override + public float[] generateReal(int sampleCount) + { + float[] samples = new float[sampleCount]; + + for(int x = 0; x < sampleCount; x++) + { + samples[x] = inphase(); + rotate(); + } + + return samples; + } + + /** + * Generates an array of complex samples from this oscillator. + * @param sampleCount number of samples to generate and length of the resulting float array. + * @return + */ + @Override + public float[] generateComplex(int sampleCount) + { + float[] samples = new float[sampleCount * 2]; + + for(int x = 0; x < sampleCount * 2; x += 2) + { + samples[x] = inphase(); + samples[x + 1] = quadrature(); + rotate(); + } + + return samples; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/IOscillator.java b/src/main/java/io/github/dsheirer/dsp/mixer/IOscillator.java new file mode 100644 index 00000000..b9e23c08 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/mixer/IOscillator.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see + * + ******************************************************************************/ +package io.github.dsheirer.dsp.mixer; + +/** + * Oscillator interface. + */ +public interface IOscillator +{ + /** + * Frequency of the tone being generated by the oscillator + * @return frequency in hertz + */ + double getFrequency(); + + /** + * Sets the frequency of the tone being generated by the oscillator. + * @param frequency in hertz + */ + void setFrequency(double frequency); + + /** + * Sample rate of the oscillator + * @return sample rate in hertz + */ + double getSampleRate(); + + /** + * Sets the sample rate of the oscillator + * @param sampleRate in hertz + */ + void setSampleRate(double sampleRate); + + + /** + * Indicates if this oscillator is enabled, meaning that it has a non-zero frequency value. + * @return true if the frequency value is non-zero + */ + boolean isEnabled(); + + /** + * Current quadrature/imaginary value of the oscillator. Note: if you are using this value iteratively, make sure + * you invoke rotate() after each time you invoke this method. + * @return current quadrature value of the oscillator. + */ + float quadrature(); + + /** + * Current inphase/real value of the oscillator. Note: if you are using this value iteratively, make sure you + * invoke rotate() after each time you invoke this method. + * @return current inphase value of the oscillator. + */ + float inphase(); + + + /** + * Rotates the oscillator by one sample period to generate new inphase() and quadrature() values. + */ + void rotate(); + + /** + * Generates the specified number of real samples into a sample array. + * @param sampleCount number of samples to generate and length of the resulting float array. + * @return generated samples + */ + float[] generateReal(int sampleCount); + + + /** + * Generates the specified number of complex samples where the samples are arranged as i0,q0,i1,q1 ... iN-1,qN-1 + * @param sampleCount number of samples to generate. + * @return generated complex samples with an array length of 2 * N + */ + float[] generateComplex(int sampleCount); + + /** + * Mixes (heterodynes) the complex sample array using the current settings of this oscillator. + * @param complexSamples to mix to a new frequency + * @return mixed/heterdyned samples + */ + float[] mixComplex(float[] complexSamples); +} diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/LowPhaseNoiseOscillator.java b/src/main/java/io/github/dsheirer/dsp/mixer/LowPhaseNoiseOscillator.java index bc33df62..86d4b786 100644 --- a/src/main/java/io/github/dsheirer/dsp/mixer/LowPhaseNoiseOscillator.java +++ b/src/main/java/io/github/dsheirer/dsp/mixer/LowPhaseNoiseOscillator.java @@ -1,42 +1,36 @@ /******************************************************************************* - * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * 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 free software: you can redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see + * You should have received a copy of the GNU General Public License along with this program. + * If not, see * ******************************************************************************/ package io.github.dsheirer.dsp.mixer; -import io.github.dsheirer.sample.complex.ComplexBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class LowPhaseNoiseOscillator +public class LowPhaseNoiseOscillator extends AbstractOscillator { private final static Logger mLog = LoggerFactory.getLogger(LowPhaseNoiseOscillator.class); private static final double TWO_PI = 2.0 * Math.PI; - private static final float THREE_HALVES = 3.0f / 2.0f; - private int mSampleRate; - private long mFrequency; - private float mInphase = 1.0f; - private float mQuadrature = 0.0f; - private float mPreviousInphase = 1.0f; - private float mPreviousQuadrature = 0.0f; - private float mCosineAngle; - private float mSineAngle; - private float mGain = 1.0f; + private static final double THREE_HALVES = 3.0 / 2.0; + private double mInphase = 1.0; + private double mQuadrature = 0.0; + private double mPreviousInphase = 1.0; + private double mPreviousQuadrature = 0.0; + private double mCosineAngle; + private double mSineAngle; + private double mGain = 1.0; /** * Ultra-low phase noise complex oscillator as described in Digital Signal Processing 3e, Lyons, p.786 @@ -44,60 +38,40 @@ public class LowPhaseNoiseOscillator * @param sampleRate for the oscillator * @param frequency to generate */ - public LowPhaseNoiseOscillator(int sampleRate, long frequency) + public LowPhaseNoiseOscillator(double frequency, double sampleRate) { - mSampleRate = sampleRate; - mFrequency = frequency; + super(frequency, sampleRate); update(); } - /** - * Sets the sample rate for this oscillator - */ - public void setSampleRate(int sampleRate) + @Override + public float quadrature() { - mSampleRate = sampleRate; - update(); + return (float)mQuadrature; } - /** - * Sample rate for this oscillator - */ - public int getSampleRate() + @Override + public float inphase() { - return mSampleRate; - } - - /** - * Sets the tone frequency generated by this oscillator - */ - public void setFrequency(long frequency) - { - mFrequency = frequency; - update(); - } - - /** - * Current tone frequency for this oscillator - */ - public long getFrequency() - { - return mFrequency; + return (float)mInphase; } /** * Updates the internal values after a frequency or sample rate change */ - private void update() + protected void update() { - double anglePerSample = TWO_PI * mFrequency / mSampleRate; + double anglePerSample = TWO_PI * getFrequency() / getSampleRate(); mCosineAngle = (float)Math.cos(anglePerSample); mSineAngle = (float)Math.sin(anglePerSample); } - private void rotate() + /** + * Rotates the oscillator to generate the next complex sample. + */ + public void rotate() { mInphase = ((mPreviousInphase * mCosineAngle) - (mPreviousQuadrature * mSineAngle)) * mGain; mQuadrature = ((mPreviousInphase * mSineAngle) + (mPreviousQuadrature * mCosineAngle)) * mGain; @@ -105,84 +79,7 @@ public class LowPhaseNoiseOscillator mPreviousInphase = mInphase; mPreviousQuadrature = mQuadrature; + //Update the gain value for the next rotation mGain = THREE_HALVES - ((mPreviousInphase * mPreviousInphase) + (mPreviousQuadrature * mPreviousQuadrature)); } - - /** - * Generates an array of samples with length of 2 x sample count containing interleaved inphase and quadrature - * samples. - * - * @param sampleCount number of samples to generate - * @return array that is sampleCount * 2 in length - */ - public float[] generate(int sampleCount) - { - float[] samples = new float[sampleCount * 2]; - - for(int x = 0; x < sampleCount; x++) - { - int index = x * 2; - samples[index] = mInphase; - samples[index + 1] = mQuadrature; - rotate(); - } - - return samples; - } - - public static void main(String[] args) - { - int sampleRate = 10000000; - int frequency = 24000; - int sampleSize = 65536; - int warmUpIterations = 100000; - int testIterations = 500000; - - LowPhaseNoiseOscillator o = new LowPhaseNoiseOscillator(sampleRate, frequency); - - mLog.debug("Warming up ..."); - - for(int x = 0; x < warmUpIterations; x++) - { - float[] samples = o.generate(sampleSize); - } - - mLog.debug("Testing ..."); - - long start = System.nanoTime(); - - for(int x = 0; x < testIterations; x++) - { - float[] samples = o.generate(sampleSize); - } - - long duration = System.nanoTime() - start; - - double durationSeconds = (double)duration / 1000000000.0; - mLog.debug("New Took:" + durationSeconds + " seconds to generate " + (testIterations * sampleSize) + " samples"); - - Oscillator o2 = new Oscillator(frequency, sampleRate); - - mLog.debug("Warming up ..."); - - for(int x = 0; x < warmUpIterations; x++) - { - ComplexBuffer samples = o2.generateComplexBuffer(sampleSize); - } - - mLog.debug("Testing ..."); - - long start2 = System.nanoTime(); - - for(int x = 0; x < testIterations; x++) - { - float[] samples = o.generate(sampleSize); - } - - long duration2 = System.nanoTime() - start2; - - double durationSeconds2 = (double)duration2 / 1000000000.0; - mLog.debug("New Took:" + durationSeconds2 + " seconds to generate " + (testIterations * sampleSize) + " samples"); - - } } diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/Oscillator.java b/src/main/java/io/github/dsheirer/dsp/mixer/Oscillator.java index a5354ded..3e87a0ba 100644 --- a/src/main/java/io/github/dsheirer/dsp/mixer/Oscillator.java +++ b/src/main/java/io/github/dsheirer/dsp/mixer/Oscillator.java @@ -1,170 +1,68 @@ /******************************************************************************* - * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * 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 free software: you can redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see + * You should have received a copy of the GNU General Public License along with this program. + * If not, see * ******************************************************************************/ package io.github.dsheirer.dsp.mixer; import io.github.dsheirer.sample.complex.Complex; -import io.github.dsheirer.sample.complex.ComplexBuffer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -public class Oscillator +public class Oscillator extends AbstractOscillator { - private final static Logger mLog = LoggerFactory.getLogger(Oscillator.class); - - private boolean mEnabled = true; - private double mFrequency; - private double mSampleRate; - private Complex mAnglePerSample; private Complex mCurrentAngle = new Complex(0.0f, -1.0f); /** - * Oscillator produces complex or float samples corresponding to a sine wave - * oscillating at the specified frequency and sample rate + * Oscillator that produces complex or float samples corresponding to a sine wave oscillating at the specified + * frequency and sample rate * * @param frequency - positive or negative frequency in hertz * @param sampleRate - in hertz */ - public Oscillator(long frequency, double sampleRate) + public Oscillator(double frequency, double sampleRate) { - mEnabled = frequency != 0; - - mSampleRate = sampleRate; - mFrequency = (double)frequency; - - update(); - } - - /** - * Indicates if this oscillator has a non-zero frequency setting. When used as a frequency translating - * mixer, setting a frequency of zero indicates that no frequency correction is needed, so this flag can - * indicate when to apply the oscillator to frequency correct samples. - * - * @return true if the current frequency value is non-zero. - */ - public boolean enabled() - { - return mEnabled; - } - - /** - * Updates the internal values after a frequency or sample rate change - */ - private void update() - { - float anglePerSample = - (float)(2.0d * Math.PI * mFrequency / mSampleRate); - - mAnglePerSample = Complex.fromAngle(anglePerSample); - } - - /** - * Sets or changes the frequency of this oscillator - */ - public void setFrequency(long frequency) - { - mEnabled = frequency != 0; - mFrequency = (double)frequency; - update(); - } - - /** - * Sets or changes the sample rate of this oscillator - */ - public void setSampleRate(double sampleRate) - { - mSampleRate = sampleRate; - update(); + super(frequency, sampleRate); } /** * Steps the current angle by the angle per sample amount */ + @Override public void rotate() { mCurrentAngle.multiply(mAnglePerSample); mCurrentAngle.fastNormalize(); } + @Override public float inphase() { return mCurrentAngle.inphase(); } + @Override public float quadrature() { return mCurrentAngle.quadrature(); } /** - * Get next complex sample + * Updates the internal values after a frequency or sample rate change */ - public Complex getComplex() + @Override + protected void update() { - return mCurrentAngle.copy(); - } - - /** - * Get the next float sample - */ - public float getFloat() - { - return mCurrentAngle.real(); - } - - /** - * Generates the requested number of complex samples and returns them in a complex buffer - * - * @param sampleCount to generate - * @return complex buffer containing the requested complex samples - */ - public ComplexBuffer generateComplexBuffer(int sampleCount) - { - float[] buffer = new float[sampleCount * 2]; - - for(int x = 0; x < sampleCount; x++) - { - int index = 2 * x; - buffer[index] = inphase(); - buffer[index + 1] = quadrature(); - - rotate(); - } - - return new ComplexBuffer(buffer); - } - - /** - * Performs complex heterodyne against the samples using this oscillator - * @param samples to mix with this oscillator - */ - public void mixComplex(float[] samples) - { - for(int x = 0; x < samples.length; x += 2) - { - float i = Complex.multiplyInphase(samples[x], samples[x + 1], inphase(), quadrature()); - float q = Complex.multiplyQuadrature(samples[x], samples[x + 1], inphase(), quadrature()); - - samples[x] = i; - samples[x + 1] = q; - - rotate(); - } + float anglePerSample = (float)(2.0d * Math.PI * getFrequency() / getSampleRate()); + mAnglePerSample = Complex.fromAngle(anglePerSample); } } diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/CICTunerChannelSource.java b/src/main/java/io/github/dsheirer/source/tuner/channel/CICTunerChannelSource.java index 0e3625bd..ddd74ab4 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/channel/CICTunerChannelSource.java +++ b/src/main/java/io/github/dsheirer/source/tuner/channel/CICTunerChannelSource.java @@ -1,6 +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 @@ -18,6 +18,7 @@ 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.mixer.IOscillator; import io.github.dsheirer.dsp.mixer.Oscillator; import io.github.dsheirer.sample.Buffer; import io.github.dsheirer.sample.Listener; @@ -47,7 +48,7 @@ public class CICTunerChannelSource extends TunerChannelSource private OverflowableTransferQueue mBuffer; - private Oscillator mFrequencyCorrectionMixer; + private IOscillator mFrequencyCorrectionMixer; private ComplexPrimeCICDecimate mDecimationFilter; private Listener mListener; private List mSampleBuffers = new ArrayList(); 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 217f7409..7137b611 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 @@ -174,6 +174,7 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc { //Notification events from the producer case NOTIFICATION_FREQUENCY_CHANGE: + mLog.debug("Processing Source Event - setting frequency to " + sourceEvent.getValue().longValue()); setFrequency(sourceEvent.getValue().longValue()); break; case NOTIFICATION_SAMPLE_RATE_CHANGE: diff --git a/src/main/java/io/github/dsheirer/source/tuner/test/SampleGenerator.java b/src/main/java/io/github/dsheirer/source/tuner/test/SampleGenerator.java index 76e7642b..b4a09a40 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/test/SampleGenerator.java +++ b/src/main/java/io/github/dsheirer/source/tuner/test/SampleGenerator.java @@ -15,6 +15,7 @@ ******************************************************************************/ package io.github.dsheirer.source.tuner.test; +import io.github.dsheirer.dsp.mixer.IOscillator; import io.github.dsheirer.dsp.mixer.LowPhaseNoiseOscillator; import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; @@ -32,7 +33,7 @@ public class SampleGenerator private final static Logger mLog = LoggerFactory.getLogger(SampleGenerator.class); private Broadcaster mComplexBufferBroadcaster = new Broadcaster<>(); - private LowPhaseNoiseOscillator mOscillator; + private IOscillator mOscillator; private int mSweepUpdateInterval; private long mInterval; private int mSamplesPerInterval; @@ -57,7 +58,7 @@ public class SampleGenerator throw new IllegalArgumentException("Sweep update rate cannot be greater than sample rate"); } - mOscillator = new LowPhaseNoiseOscillator(sampleRate, frequency); + mOscillator = new LowPhaseNoiseOscillator(frequency, sampleRate); mInterval = interval; mSweepUpdateInterval = sweepUpdateRate; @@ -175,7 +176,7 @@ public class SampleGenerator */ public long getFrequency() { - return mOscillator.getFrequency(); + return (long)mOscillator.getFrequency(); } /** @@ -213,15 +214,15 @@ public class SampleGenerator mReusableBuffer.getSamples().length != mSamplesPerInterval || mOscillatorFrequency != mOscillator.getFrequency()) { - mOscillatorFrequency = mOscillator.getFrequency(); - mReusableBuffer = new TimestampedComplexBuffer(mOscillator.generate(mSamplesPerInterval)); + mOscillatorFrequency = (long)mOscillator.getFrequency(); + mReusableBuffer = new TimestampedComplexBuffer(mOscillator.generateComplex(mSamplesPerInterval)); } mComplexBufferBroadcaster.receive(mReusableBuffer); } else { - float[] samples = mOscillator.generate(mSamplesPerInterval); + float[] samples = mOscillator.generateComplex(mSamplesPerInterval); mComplexBufferBroadcaster.receive(new TimestampedComplexBuffer(samples)); } @@ -233,7 +234,7 @@ public class SampleGenerator { mTriggerInterval = 0; - long updatedFrequency = mOscillator.getFrequency() + mSweepUpdateInterval; + long updatedFrequency = (long)mOscillator.getFrequency() + mSweepUpdateInterval; if(updatedFrequency > mOscillator.getSampleRate() / 2) { 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 e8f980ba..bedb5bfd 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 = 225000; + public static final int SAMPLE_RATE = 50000; public static final int DC_NOISE_BANDWIDTH = 0; public static final double USABLE_BANDWIDTH = 1.00; diff --git a/src/main/resources/FilterView.css b/src/main/resources/FilterView.css index eed6bc12..3577be48 100644 --- a/src/main/resources/FilterView.css +++ b/src/main/resources/FilterView.css @@ -1,12 +1,12 @@ /* ....Show License.... */ .chart { - -fx-background-image: url("../../../images/Curve-fitted-background.png"); + -fx-background-image: url("/../resources/images/Curve-fitted-background.png"); -fx-padding: 15 25 15 15; } .chart-plot-background { -fx-background-color: red; - -fx-background-image: url("../../../images/Curve-fitted-chart-background.png"), - url("../../../images/Curve-fitted-graph-gridlines.png"); + -fx-background-image: url("/../resources/images/Curve-fitted-chart-background.png"), + url("/../resources/images/Curve-fitted-graph-gridlines.png"); -fx-background-size: cover, auto; -fx-background-repeat: no-repeat, repeat; -fx-background-position: 0% 0%, 0% 100%;