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%;