mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-14 17:47:32 -05:00
[WIP] working on polyphase channelizer and testing viewer
This commit is contained in:
106
src/dsp/filter/channelizer/ChannelDistributor.java
Normal file
106
src/dsp/filter/channelizer/ChannelDistributor.java
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* sdrtrunk
|
||||||
|
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*
|
||||||
|
******************************************************************************/
|
||||||
|
package dsp.filter.channelizer;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sample.Broadcaster;
|
||||||
|
import sample.Listener;
|
||||||
|
import sample.complex.ComplexBuffer;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ChannelDistributor
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(ChannelDistributor.class);
|
||||||
|
private Map<Integer,ComplexSampleAssembler> mListenerMap = new HashMap<>();
|
||||||
|
private int mBufferSize;
|
||||||
|
private int mChannelCount;
|
||||||
|
|
||||||
|
public ChannelDistributor(int bufferSize, int channelCount)
|
||||||
|
{
|
||||||
|
mBufferSize = bufferSize;
|
||||||
|
mChannelCount = channelCount;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void receive(float[] channels)
|
||||||
|
{
|
||||||
|
for(int channel = 0; channel < channels.length / 2; channel++)
|
||||||
|
{
|
||||||
|
if(mListenerMap.containsKey(channel))
|
||||||
|
{
|
||||||
|
ComplexSampleAssembler channelAssembler = mListenerMap.get(channel);
|
||||||
|
|
||||||
|
int index = channel * 2;
|
||||||
|
channelAssembler.receive(channels[index], channels[index + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addListener(int channel, Listener<ComplexBuffer> listener)
|
||||||
|
{
|
||||||
|
ComplexSampleAssembler channelAssembler = mListenerMap.get(channel);
|
||||||
|
|
||||||
|
if(channelAssembler == null)
|
||||||
|
{
|
||||||
|
channelAssembler = new ComplexSampleAssembler(mBufferSize);
|
||||||
|
mListenerMap.put(channel, channelAssembler);
|
||||||
|
}
|
||||||
|
|
||||||
|
channelAssembler.addListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ComplexSampleAssembler
|
||||||
|
{
|
||||||
|
private Broadcaster<ComplexBuffer> mBroadcaster = new Broadcaster<>();
|
||||||
|
private float[] mBuffer;
|
||||||
|
private int mBufferPointer;
|
||||||
|
private int mBufferSize;
|
||||||
|
private boolean mEnableLogging;
|
||||||
|
|
||||||
|
public ComplexSampleAssembler(int bufferSize)
|
||||||
|
{
|
||||||
|
mBufferSize = bufferSize;
|
||||||
|
mBuffer = new float[mBufferSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void receive(float inphase, float quadrature)
|
||||||
|
{
|
||||||
|
mBuffer[mBufferPointer++] = inphase;
|
||||||
|
mBuffer[mBufferPointer++] = inphase;
|
||||||
|
|
||||||
|
if(mBufferPointer >= mBufferSize)
|
||||||
|
{
|
||||||
|
float[] bufferCopy = new float[mBuffer.length];
|
||||||
|
System.arraycopy(mBuffer,0, bufferCopy, 0, mBuffer.length);
|
||||||
|
|
||||||
|
mBroadcaster.receive(new ComplexBuffer(bufferCopy));
|
||||||
|
|
||||||
|
mBufferPointer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addListener(Listener<ComplexBuffer> listener)
|
||||||
|
{
|
||||||
|
mBroadcaster.addListener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
package dsp.filter.channelizer;
|
package dsp.filter.channelizer;
|
||||||
|
|
||||||
import dsp.filter.FilterFactory;
|
import dsp.filter.FilterFactory;
|
||||||
|
import dsp.mixer.LowPhaseNoiseOscillator;
|
||||||
import dsp.mixer.Oscillator;
|
import dsp.mixer.Oscillator;
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -35,6 +36,8 @@ import spectrum.SpectrumPanel;
|
|||||||
import spectrum.converter.ComplexDecibelConverter;
|
import spectrum.converter.ComplexDecibelConverter;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.ChangeEvent;
|
||||||
|
import javax.swing.event.ChangeListener;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
@@ -45,14 +48,14 @@ public class ChannelizerViewer extends JFrame
|
|||||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelizerViewer.class);
|
private final static Logger mLog = LoggerFactory.getLogger(ChannelizerViewer.class);
|
||||||
|
|
||||||
private static final int CHANNEL_BANDWIDTH = 12500;
|
private static final int CHANNEL_BANDWIDTH = 12500;
|
||||||
|
private static final int CHANNEL_FFT_FRAME_RATE = 20;
|
||||||
|
|
||||||
private SettingsManager mSettingsManager = new SettingsManager(new TunerConfigurationModel());
|
private SettingsManager mSettingsManager = new SettingsManager(new TunerConfigurationModel());
|
||||||
private JPanel mPrimaryPanel;
|
private JPanel mPrimaryPanel;
|
||||||
private SpectrumPanel mSpectrumPanel;
|
private JPanel mControlPanel;
|
||||||
private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter();
|
private ChannelPanel mPrimarySpectrumPanel;
|
||||||
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
|
private ChannelArrayPanel mChannelPanel;
|
||||||
private ChannelPanel mChannelPanel;
|
private LowPhaseNoiseOscillator mOscillator;
|
||||||
private Oscillator mOscillator;
|
|
||||||
private int mChannelCount;
|
private int mChannelCount;
|
||||||
private int mSampleRate;
|
private int mSampleRate;
|
||||||
private int mToneFrequency;
|
private int mToneFrequency;
|
||||||
@@ -62,7 +65,7 @@ public class ChannelizerViewer extends JFrame
|
|||||||
{
|
{
|
||||||
mChannelCount = channelCount;
|
mChannelCount = channelCount;
|
||||||
mSampleRate = mChannelCount * CHANNEL_BANDWIDTH;
|
mSampleRate = mChannelCount * CHANNEL_BANDWIDTH;
|
||||||
mToneFrequency = 37500; //Set to channel 1 as default
|
mToneFrequency = (int)(CHANNEL_BANDWIDTH * 3); //Set to channel 1 as default
|
||||||
|
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
@@ -76,13 +79,13 @@ public class ChannelizerViewer extends JFrame
|
|||||||
setLocationRelativeTo(null);
|
setLocationRelativeTo(null);
|
||||||
add(getPrimaryPanel());
|
add(getPrimaryPanel());
|
||||||
|
|
||||||
mOscillator = new Oscillator(mToneFrequency, mSampleRate);
|
mOscillator = new LowPhaseNoiseOscillator(mSampleRate, mToneFrequency);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start()
|
public void start()
|
||||||
{
|
{
|
||||||
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
|
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
|
||||||
scheduledExecutorService.scheduleAtFixedRate(new SampleGenerationTask(), 0, 100, TimeUnit.MILLISECONDS);
|
scheduledExecutorService.scheduleAtFixedRate(new SampleGenerationTask(), 0, 50, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setToneFrequency(int frequency)
|
public void setToneFrequency(int frequency)
|
||||||
@@ -93,22 +96,14 @@ public class ChannelizerViewer extends JFrame
|
|||||||
|
|
||||||
private void generateSamples()
|
private void generateSamples()
|
||||||
{
|
{
|
||||||
int size = mDFTSize.getSize();
|
int size = mChannelCount * CHANNEL_BANDWIDTH / CHANNEL_FFT_FRAME_RATE * 2;
|
||||||
|
|
||||||
float[] samples = new float[size * 2];
|
float[] samples = mOscillator.generate(size);
|
||||||
|
|
||||||
for(int x = 0; x < size; x += 2)
|
|
||||||
{
|
|
||||||
samples[x] = mOscillator.getComplex().inphase();
|
|
||||||
samples[x + 1] = mOscillator.getComplex().quadrature();
|
|
||||||
|
|
||||||
mOscillator.rotate();
|
|
||||||
}
|
|
||||||
|
|
||||||
ComplexBuffer buffer = new ComplexBuffer(samples);
|
ComplexBuffer buffer = new ComplexBuffer(samples);
|
||||||
|
|
||||||
getChannelPanel().receive(buffer);
|
getChannelArrayPanel().receive(buffer);
|
||||||
mDFTProcessor.receive(buffer);
|
getSpectrumPanel().receive(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private JPanel getPrimaryPanel()
|
private JPanel getPrimaryPanel()
|
||||||
@@ -116,34 +111,60 @@ public class ChannelizerViewer extends JFrame
|
|||||||
if(mPrimaryPanel == null)
|
if(mPrimaryPanel == null)
|
||||||
{
|
{
|
||||||
mPrimaryPanel = new JPanel();
|
mPrimaryPanel = new JPanel();
|
||||||
mPrimaryPanel.setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[][grow,fill]"));
|
mPrimaryPanel.setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[][][grow,fill]"));
|
||||||
mPrimaryPanel.add(getSpectrumPanel(), "wrap");
|
mPrimaryPanel.add(getSpectrumPanel(), "wrap");
|
||||||
mPrimaryPanel.add(getChannelPanel());
|
mPrimaryPanel.add(getControlPanel(), "wrap");
|
||||||
|
mPrimaryPanel.add(getChannelArrayPanel());
|
||||||
}
|
}
|
||||||
|
|
||||||
return mPrimaryPanel;
|
return mPrimaryPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SpectrumPanel getSpectrumPanel()
|
private ChannelPanel getSpectrumPanel()
|
||||||
{
|
{
|
||||||
if(mSpectrumPanel == null)
|
if(mPrimarySpectrumPanel == null)
|
||||||
{
|
{
|
||||||
mSpectrumPanel = new SpectrumPanel(mSettingsManager);
|
mPrimarySpectrumPanel = new ChannelPanel(mSettingsManager, mSampleRate);
|
||||||
mSpectrumPanel.setPreferredSize(new Dimension(1000, 200));
|
mPrimarySpectrumPanel.setPreferredSize(new Dimension(1000, 200));
|
||||||
mComplexDecibelConverter.addListener(mSpectrumPanel);
|
mPrimarySpectrumPanel.setDFTSize(DFTSize.FFT32768);
|
||||||
mDFTProcessor.addConverter(mComplexDecibelConverter);
|
|
||||||
mDFTProcessor.frequencyChanged(new FrequencyChangeEvent(
|
|
||||||
FrequencyChangeEvent.Event.NOTIFICATION_SAMPLE_RATE_CHANGE, mSampleRate));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return mSpectrumPanel;
|
return mPrimarySpectrumPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ChannelPanel getChannelPanel()
|
private JPanel getControlPanel()
|
||||||
|
{
|
||||||
|
if(mControlPanel == null)
|
||||||
|
{
|
||||||
|
mControlPanel = new JPanel();
|
||||||
|
|
||||||
|
int maximumFrequency = mSampleRate / 2;
|
||||||
|
|
||||||
|
SpinnerNumberModel model = new SpinnerNumberModel(mToneFrequency, -maximumFrequency, maximumFrequency, CHANNEL_BANDWIDTH );
|
||||||
|
|
||||||
|
model.addChangeListener(new ChangeListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void stateChanged(ChangeEvent e)
|
||||||
|
{
|
||||||
|
int frequency = model.getNumber().intValue();
|
||||||
|
mOscillator.setFrequency(frequency);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
JSpinner spinner = new JSpinner(model);
|
||||||
|
|
||||||
|
mControlPanel.add(spinner);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mControlPanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChannelArrayPanel getChannelArrayPanel()
|
||||||
{
|
{
|
||||||
if(mChannelPanel == null)
|
if(mChannelPanel == null)
|
||||||
{
|
{
|
||||||
mChannelPanel = new ChannelPanel(getFilter());
|
mChannelPanel = new ChannelArrayPanel(getFilter());
|
||||||
}
|
}
|
||||||
|
|
||||||
return mChannelPanel;
|
return mChannelPanel;
|
||||||
@@ -170,13 +191,47 @@ public class ChannelizerViewer extends JFrame
|
|||||||
return taps;
|
return taps;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ChannelPanel extends JPanel implements Listener<ComplexBuffer>
|
public class ChannelArrayPanel extends JPanel implements Listener<ComplexBuffer>
|
||||||
{
|
{
|
||||||
private PolyphaseChannelizer mPolyphaseChannelizer;
|
private PolyphaseChannelizer mPolyphaseChannelizer;
|
||||||
|
private ChannelDistributor mChannelDistributor;
|
||||||
|
|
||||||
public ChannelPanel(float[] taps)
|
public ChannelArrayPanel(float[] taps)
|
||||||
{
|
{
|
||||||
|
int bufferSize = CHANNEL_BANDWIDTH / CHANNEL_FFT_FRAME_RATE;
|
||||||
|
if(bufferSize % 2 == 1)
|
||||||
|
{
|
||||||
|
bufferSize++;
|
||||||
|
}
|
||||||
|
|
||||||
mPolyphaseChannelizer = new PolyphaseChannelizer(taps, mChannelCount);
|
mPolyphaseChannelizer = new PolyphaseChannelizer(taps, mChannelCount);
|
||||||
|
mChannelDistributor = new ChannelDistributor(bufferSize, mChannelCount);
|
||||||
|
mPolyphaseChannelizer.setChannelDistributor(mChannelDistributor);
|
||||||
|
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init()
|
||||||
|
{
|
||||||
|
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill][grow,fill][grow,fill][grow,fill]", ""));
|
||||||
|
|
||||||
|
for(int x = 0; x < mChannelCount; x++)
|
||||||
|
{
|
||||||
|
ChannelPanel channelPanel = new ChannelPanel(mSettingsManager, CHANNEL_BANDWIDTH * 2);
|
||||||
|
channelPanel.setPreferredSize(new Dimension(250,100));
|
||||||
|
channelPanel.setDFTSize(DFTSize.FFT00512);
|
||||||
|
|
||||||
|
if(x % 4 == 3)
|
||||||
|
{
|
||||||
|
add(channelPanel, "wrap");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
add(channelPanel);
|
||||||
|
}
|
||||||
|
|
||||||
|
mChannelDistributor.addListener(x, channelPanel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -186,6 +241,36 @@ public class ChannelizerViewer extends JFrame
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ChannelPanel extends JPanel implements Listener<ComplexBuffer>
|
||||||
|
{
|
||||||
|
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
|
||||||
|
private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter();
|
||||||
|
private SpectrumPanel mSpectrumPanel;
|
||||||
|
|
||||||
|
public ChannelPanel(SettingsManager settingsManager, int sampleRate)
|
||||||
|
{
|
||||||
|
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||||
|
mSpectrumPanel = new SpectrumPanel(settingsManager);
|
||||||
|
add(mSpectrumPanel);
|
||||||
|
|
||||||
|
mDFTProcessor.addConverter(mComplexDecibelConverter);
|
||||||
|
mDFTProcessor.frequencyChanged(new FrequencyChangeEvent(
|
||||||
|
FrequencyChangeEvent.Event.NOTIFICATION_SAMPLE_RATE_CHANGE, sampleRate));
|
||||||
|
mComplexDecibelConverter.addListener(mSpectrumPanel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDFTSize(DFTSize dftSize)
|
||||||
|
{
|
||||||
|
mDFTProcessor.setDFTSize(dftSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receive(ComplexBuffer complexBuffer)
|
||||||
|
{
|
||||||
|
mDFTProcessor.receive(complexBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class SampleGenerator
|
public class SampleGenerator
|
||||||
{
|
{
|
||||||
public SampleGenerator()
|
public SampleGenerator()
|
||||||
@@ -218,7 +303,9 @@ public class ChannelizerViewer extends JFrame
|
|||||||
|
|
||||||
public static void main(String[] args)
|
public static void main(String[] args)
|
||||||
{
|
{
|
||||||
final ChannelizerViewer frame = new ChannelizerViewer(16);
|
int channelCount = 24;
|
||||||
|
|
||||||
|
final ChannelizerViewer frame = new ChannelizerViewer(channelCount);
|
||||||
|
|
||||||
EventQueue.invokeLater(new Runnable()
|
EventQueue.invokeLater(new Runnable()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ public class PolyphaseChannelizer implements Listener<ComplexBuffer>
|
|||||||
private float[] mFilteredSamples;
|
private float[] mFilteredSamples;
|
||||||
private boolean mToggle = false;
|
private boolean mToggle = false;
|
||||||
private FloatFFT_1D mFFT;
|
private FloatFFT_1D mFFT;
|
||||||
|
private ChannelDistributor mChannelDistributor;
|
||||||
|
|
||||||
private DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0000");
|
private DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0000");
|
||||||
|
|
||||||
@@ -125,15 +126,15 @@ public class PolyphaseChannelizer implements Listener<ComplexBuffer>
|
|||||||
|
|
||||||
private void dispatch(float[] channels)
|
private void dispatch(float[] channels)
|
||||||
{
|
{
|
||||||
// StringBuilder sb = new StringBuilder();
|
if(mChannelDistributor != null)
|
||||||
//
|
{
|
||||||
// for(int x = 0; x < channels.length; x += 2)
|
mChannelDistributor.receive(channels);
|
||||||
// {
|
}
|
||||||
// sb.append((x/2)).append("[")
|
}
|
||||||
// .append(DECIMAL_FORMAT.format(magnitude(channels[x], channels[x + 1], mChannelCount))).append("] ");
|
|
||||||
// }
|
public void setChannelDistributor(ChannelDistributor channelDistributor)
|
||||||
//
|
{
|
||||||
// mLog.debug("Out: " + sb.toString());
|
mChannelDistributor = channelDistributor;
|
||||||
}
|
}
|
||||||
|
|
||||||
private double magnitude(float real, float imaginary, int fftSize)
|
private double magnitude(float real, float imaginary, int fftSize)
|
||||||
|
|||||||
126
src/dsp/mixer/LowPhaseNoiseOscillator.java
Normal file
126
src/dsp/mixer/LowPhaseNoiseOscillator.java
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* sdrtrunk
|
||||||
|
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*
|
||||||
|
******************************************************************************/
|
||||||
|
package dsp.mixer;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class LowPhaseNoiseOscillator
|
||||||
|
{
|
||||||
|
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 int 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ultra-low phase noise complex oscillator as described in Digital Signal Processing 3e, Lyons, p.786
|
||||||
|
*
|
||||||
|
* @param sampleRate for the oscillator
|
||||||
|
* @param frequency to generate
|
||||||
|
*/
|
||||||
|
public LowPhaseNoiseOscillator(int sampleRate, int frequency)
|
||||||
|
{
|
||||||
|
mSampleRate = sampleRate;
|
||||||
|
mFrequency = frequency;
|
||||||
|
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSampleRate(int sampleRate)
|
||||||
|
{
|
||||||
|
mSampleRate = sampleRate;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFrequency(int frequency)
|
||||||
|
{
|
||||||
|
mFrequency = frequency;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the internal values after a frequency or sample rate change
|
||||||
|
*/
|
||||||
|
private void update()
|
||||||
|
{
|
||||||
|
double anglePerSample = TWO_PI * mFrequency / mSampleRate;
|
||||||
|
|
||||||
|
mCosineAngle = (float)Math.cos(anglePerSample);
|
||||||
|
mSineAngle = (float)Math.sin(anglePerSample);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rotate()
|
||||||
|
{
|
||||||
|
mInphase = ((mPreviousInphase * mCosineAngle) - (mPreviousQuadrature * mSineAngle)) * mGain;
|
||||||
|
mQuadrature = ((mPreviousInphase * mSineAngle) + (mPreviousQuadrature * mCosineAngle)) * mGain;
|
||||||
|
|
||||||
|
mPreviousInphase = mInphase;
|
||||||
|
mPreviousQuadrature = mQuadrature;
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
LowPhaseNoiseOscillator o = new LowPhaseNoiseOscillator(10, 1);
|
||||||
|
|
||||||
|
float[] samples = o.generate(20);
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for(int x = 0; x < samples.length; x += 2)
|
||||||
|
{
|
||||||
|
sb.append(samples[x]).append(" ").append(samples[x + 1]).append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.debug("Samples:\n" + sb.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import sample.complex.Complex;
|
import sample.complex.Complex;
|
||||||
|
import sample.complex.ComplexBuffer;
|
||||||
|
|
||||||
public class Oscillator
|
public class Oscillator
|
||||||
{
|
{
|
||||||
@@ -110,4 +111,25 @@ public class Oscillator
|
|||||||
{
|
{
|
||||||
return mCurrentAngle.real();
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user