diff --git a/src/dsp/filter/channelizer/AbstractComplexPolyphaseChannelizer.java b/src/dsp/filter/channelizer/AbstractComplexPolyphaseChannelizer.java index fc7a0f06..273bf23e 100644 --- a/src/dsp/filter/channelizer/AbstractComplexPolyphaseChannelizer.java +++ b/src/dsp/filter/channelizer/AbstractComplexPolyphaseChannelizer.java @@ -92,9 +92,11 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener { private final static Logger mLog = LoggerFactory.getLogger(Channelizer.class); + private static final int CHANNEL_BANDWIDTH = 12500; + private static final double CHANNEL_OVERSAMPLING = 2.0; private Tuner mTuner; private List mChannelSources = new CopyOnWriteArrayList<>(); + private ChannelizerConfiguration mChannelizerConfiguration; /** * Provides access to Digital Drop Channel (DDC) sources from the tuner. Incorporates a polyphase channelizer @@ -46,12 +49,47 @@ public class Channelizer implements ISourceEventProcessor, Listener public Channelizer(Tuner tuner) { mTuner = tuner; + + int sampleRate = mTuner.getTunerController().getSampleRate(); + + if(sampleRate % 25000 != 0) + { + throw new IllegalArgumentException("Tuner sample rate [" + sampleRate + "] must be a multiple of 25 kHz"); + } + + int channelCount = sampleRate / CHANNEL_BANDWIDTH; + + long frequency = mTuner.getTunerController().getFrequency(); + + mChannelizerConfiguration = new ChannelizerConfiguration(frequency, channelCount, + CHANNEL_BANDWIDTH, CHANNEL_OVERSAMPLING); + mTuner.addSourceEventListener(this); } + /** + * Provides a Digital Drop Channel (DDC) for the specified tuner channel or returns null if the channel can't be + * sourced due to the current center frequency and/or sample rate. + * @param tunerChannel specifying center frequency and bandwidth. + * @return source or null. + */ public Source getChannel(TunerChannel tunerChannel) { - //TODO: return a polyphase channel here + try + { + List polyphaseIndexes = mChannelizerConfiguration.getPolyphaseChannelIndexes(tunerChannel); + + + + } + catch(IllegalArgumentException iae) + { + mLog.info("Can't provide DDC for " + tunerChannel.toString() + " due to channelizer frequency [" + + " due to channelizer frequency [" + mChannelizerConfiguration.getCenterFrequency() + + "] and sample rate [" + (mChannelizerConfiguration.getChannelCount() * + mChannelizerConfiguration.getChannelBandwidth()) + "]"); + } + return null; } @@ -121,6 +159,16 @@ public class Channelizer implements ISourceEventProcessor, Listener (sourceEvent.hasSource() ? sourceEvent.getSource().getClass() : "null source")); } break; + case NOTIFICATION_FREQUENCY_CHANGE: + mChannelizerConfiguration.setCenterFrequency(sourceEvent.getValue().longValue()); + updateChannelResultsProcessors(); + break; + case NOTIFICATION_SAMPLE_RATE_CHANGE: + int sampleRate = sourceEvent.getValue().intValue(); + int channelCount = sampleRate / mChannelizerConfiguration.getChannelBandwidth(); + mChannelizerConfiguration.setChannelCount(channelCount); + updateChannelResultsProcessors(); + break; } @@ -130,6 +178,11 @@ public class Channelizer implements ISourceEventProcessor, Listener @Override public void receive(SourceEvent sourceEvent) { + process(sourceEvent); + } + private void updateChannelResultsProcessors() + { + //Update the processors anytime that the center frequency or sample rate changes } } diff --git a/src/dsp/filter/channelizer/ChannelizerConfiguration.java b/src/dsp/filter/channelizer/ChannelizerConfiguration.java new file mode 100644 index 00000000..be89ac42 --- /dev/null +++ b/src/dsp/filter/channelizer/ChannelizerConfiguration.java @@ -0,0 +1,310 @@ +/******************************************************************************* + * 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 + * + ******************************************************************************/ +package dsp.filter.channelizer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import source.tuner.TunerChannel; + +import java.util.ArrayList; +import java.util.List; + +public class ChannelizerConfiguration +{ + private final static Logger mLog = LoggerFactory.getLogger(ChannelizerConfiguration.class); + + private long mCenterFrequency; + private int mChannelCount; + private int mChannelBandwidth; + private double mOversampling = 1.0; + + /** + * Configuration details for a polyphase channelizer that can be used to identify which polyphase + * channelizer output channel(s) should be processed to produce an output channel of a specified + * center frequency and channel sample rate/bandwidth. + * + * @param centerFrequency + * @param channelCount + * @param channelBandwidth + * @param oversampling set to 1.0 for no oversample, or greater than 1.0 for oversampling + */ + public ChannelizerConfiguration(long centerFrequency, int channelCount, int channelBandwidth, double oversampling) + { + mCenterFrequency = centerFrequency; + mChannelCount = channelCount; + mChannelBandwidth = channelBandwidth; + mOversampling = oversampling; + } + + /** + * Center frequency for the channelizer + */ + public long getCenterFrequency() + { + return mCenterFrequency; + } + + /** + * Sets or changes the center frequency for the channelizer + */ + public void setCenterFrequency(long frequency) + { + mCenterFrequency = frequency; + } + + /** + * Number of channels being channelized by the polyphase channelizer + */ + public int getChannelCount() + { + return mChannelCount; + } + + /** + * Sets the number of channels being channelized by the polyphase channelizer + */ + public void setChannelCount(int channelCount) + { + mChannelCount = channelCount; + } + + /** + * Channel bandwidth. Note: this value may be smaller than the channel sample rate when oversampling + * is being used. + */ + public int getChannelBandwidth() + { + return mChannelBandwidth; + } + + /** + * Half of the channel bandwidth. + */ + public int getHalfChannelBandwidth() + { + return mChannelBandwidth / 2; + } + + /** + * Sets the channel bandwidth in hertz. + */ + public void setChannelBandwidth(int channelBandwidth) + { + mChannelBandwidth = channelBandwidth; + } + + /** + * Oversampling rate produced by the channelizer. + * + * @return 1.0 for no oversampling, or a value greater than 1.0 when oversampling is applied. + */ + public double getOversampling() + { + return mOversampling; + } + + /** + * Sets the oversampling rate being applied by the channelizer + * @param oversampling rate where 1.0 indicates no oversampling, and a value greater than 1.0 indicates + * that the polyphase channelizer is oversampling each channel. + */ + public void setOversampling(double oversampling) + { + mOversampling = oversampling; + } + + /** + * Channel sample rate. This value is the product of the filtered channel bandwidth times the + * oversampling rate. + */ + public double getChannelSampleRate() + { + return (double)getChannelBandwidth() * getOversampling(); + } + + /** + * Channelizer input sample rate + * @return input sample rate in hertz + */ + public int getSampleRate() + { + return getChannelBandwidth() * getChannelCount(); + } + + /** + * Channelizer input minimum frequency + * @return minimum frequency in hertz + */ + public long getMinimumFrequency() + { + return getCenterFrequency() - (getSampleRate() / 2); + } + + /** + * Channelizer input maximum frequency + * @return maximum frequency in hertz + */ + public long getMaximumFrequency() + { + return getCenterFrequency() + (getSampleRate() / 2); + } + + /** + * Identifies the channelizer output channel(s) required to produce a tuner channel with the specified center + * frequency and channel bandwidth. + * + * @param tunerChannel specifying the desired channel frequency and bandwidth + * @return an array of one or more polyphase channelizer channels to target in order to produce an output channel + * for the desired frequency and bandwidth. + * @throws IllegalArgumentException if the tuner channel's minimum or maximum frequency is not contained + * within the channelizer's minimum or maximum frequency range + */ + public List getPolyphaseChannelIndexes(TunerChannel tunerChannel) throws IllegalArgumentException + { + if(tunerChannel.getMinFrequency() < getMinimumFrequency() || + tunerChannel.getMaxFrequency() > getMaximumFrequency()) + { + throw new IllegalArgumentException("Requested channel cannot be provided by this " + + "channelizer. Requested channel [" + tunerChannel.getMinFrequency() + " - " + + tunerChannel.getMaxFrequency() + "] exceeds current channelizer frequency range [" + + getMinimumFrequency() + " - " + getMaximumFrequency() + "]"); + } + + List indices = new ArrayList<>(); + + long startFrequencyOffset = (tunerChannel.getMinFrequency() - getMinimumFrequency()); + int startIndex = (int)(startFrequencyOffset / getChannelBandwidth()); + + long endFrequencyOffset = (tunerChannel.getMaxFrequency() - getMinimumFrequency()); + int endIndex = (int)(endFrequencyOffset / getChannelBandwidth()); + + int currentIndex = startIndex; + + while(currentIndex <= endIndex) + { + long currentIndexMin = getMinimumFrequencyForChannelIndex(currentIndex); + long currentIndexMax = getMaximumFrequencyForChannelIndex(currentIndex); + + //Case 1: tuner channel completely aligns with indexed channel + if(currentIndexMin <= tunerChannel.getMinFrequency() && tunerChannel.getMaxFrequency() <= currentIndexMax) + { + indices.add(currentIndex); + } + //Case 2: tuner channel min frequency aligns and max frequency exceeds + else if(currentIndexMin <= tunerChannel.getMinFrequency() && + tunerChannel.getMinFrequency() < currentIndexMax && + tunerChannel.getMaxFrequency() > currentIndexMax) + { + indices.add(currentIndex); + } + //Case 3: tuner channel min frequency exceeds and max frequency aligns + else if(tunerChannel.getMinFrequency() < currentIndexMin && + tunerChannel.getMaxFrequency() > currentIndexMin && + tunerChannel.getMaxFrequency() <= currentIndexMax) + { + indices.add(currentIndex); + } + //Case 4: tuner channel encapsulates the indexed channel + else if(tunerChannel.getMinFrequency() < currentIndexMin && tunerChannel.getMaxFrequency() > currentIndexMax) + { + indices.add(currentIndex); + } + + currentIndex++; + } + + return indices; + } + + /** + * Calculates the center frequency for the channel identified by the index in the channel results array. + * + * @param index to determine the channel center frequency + * @return center frequency for the indexed channel + */ + public long getCenterFrequencyForChannelIndex(int index) + { + if(index < 0 || index >= getChannelCount()) + { + throw new IllegalArgumentException("Illegal channel index"); + } + + return getMinimumFrequency() + getHalfChannelBandwidth() + (index * getChannelBandwidth()); + } + + /** + * Calculates the minimum frequency band edge for the channel identified by the index in the channel results + * array. + * @param index to calculate the minimum band edge frequency for the channel. + * @return frequency of the minimum band edge + */ + public long getMinimumFrequencyForChannelIndex(int index) + { + if(index < 0 || index > getChannelCount()) + { + throw new IllegalArgumentException("Can't determine minimum frequency - illegal channel index [" + + index + "]"); + } + + return getMinimumFrequency() + (index * getChannelBandwidth()); + } + + /** + * Calculates the maximum frequency band edge for the channel identified by the index in the channel results + * array. + * @param index to calculate the maximum band edge frequency for the channel. + * @return frequency of the maximum band edge + */ + public long getMaximumFrequencyForChannelIndex(int index) + { + if(index < 0 || index > getChannelCount()) + { + throw new IllegalArgumentException("Can't determine maximum frequency - illegal channel index [" + + index + "]"); + } + + //Maximum frequency is the same as the minimum frequency of the next higher indexed channel + return getMinimumFrequency() + ((index + 1) * getChannelBandwidth()); + } + + public static void main(String[] args) + { + mLog.debug("Starting ...."); + + int channelCount = 10; + + ChannelizerConfiguration channelizerConfiguration = + new ChannelizerConfiguration(100062500, channelCount, 12500, 2.0); + + for(int x = 0; x < channelCount; x++) + { + mLog.debug("Channel " + x + ": " + channelizerConfiguration.getMinimumFrequencyForChannelIndex(x) + " - " + + channelizerConfiguration.getMaximumFrequencyForChannelIndex(x) + + " Center: " + channelizerConfiguration.getCenterFrequencyForChannelIndex(x)); + } + + TunerChannel tunerChannel = new TunerChannel(100006250, 12500); + + List indices = channelizerConfiguration.getPolyphaseChannelIndexes(tunerChannel); + + mLog.debug("Indices: " + indices); + + mLog.debug("Finished!"); + } +} diff --git a/src/dsp/filter/channelizer/ChannelizerViewer.java b/src/dsp/filter/channelizer/ChannelizerViewer.java index 09d80c3f..92ba830b 100644 --- a/src/dsp/filter/channelizer/ChannelizerViewer.java +++ b/src/dsp/filter/channelizer/ChannelizerViewer.java @@ -224,7 +224,7 @@ public class ChannelizerViewer extends JFrame mPolyphaseChannelizer = new ComplexPolyphaseChannelizer(taps, mChannelCount, CHANNEL_BANDWIDTH); mChannelDistributor = new ChannelDistributor(bufferSize, mChannelCount); - mPolyphaseChannelizer.setChannelDistributor(mChannelDistributor); +// mPolyphaseChannelizer.setChannelDistributor(mChannelDistributor); init(); } diff --git a/src/dsp/filter/channelizer/PolyphaseChannelSource.java b/src/dsp/filter/channelizer/PolyphaseChannelSource.java index 6050491b..eb15f6cb 100644 --- a/src/dsp/filter/channelizer/PolyphaseChannelSource.java +++ b/src/dsp/filter/channelizer/PolyphaseChannelSource.java @@ -20,16 +20,15 @@ package dsp.filter.channelizer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sample.Listener; +import sample.OverflowableTransferQueue; import sample.complex.ComplexBuffer; +import sample.real.IOverflowListener; import source.ISourceEventProcessor; import source.SourceEvent; import source.SourceException; import source.tuner.ComplexChannelSource; import source.tuner.TunerChannel; -import java.util.concurrent.ScheduledExecutorService; - public class PolyphaseChannelSource extends ComplexChannelSource { private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class); @@ -40,6 +39,7 @@ public class PolyphaseChannelSource extends ComplexChannelSource private int mChannelSampleRate; private int mChannelCount; private int mOutputFrequencyCorrection; + private ChannelBufferProcessor mProcessor = new ChannelBufferProcessor(); /** * Polyphase channelizer channel implementation. Adapts the channel array output samples from the polyphase @@ -86,13 +86,11 @@ public class PolyphaseChannelSource extends ComplexChannelSource /** * Process the array samples for each of the output channels from the polyphase channelizer. - * @param channels + * @param channelsBuffer */ - public void processChannels(float[] channels) + public void processChannels(ComplexBuffer channelsBuffer) { - - //TODO we have to dispatch full buffers on a separate thread so that we don't bog down the channelizer - //TODO thread that is delivering the channel samples to us. + mProcessor.process(channelsBuffer); } /** @@ -198,4 +196,46 @@ public class PolyphaseChannelSource extends ComplexChannelSource { } + + public class ChannelBufferProcessor implements Runnable + { + private OverflowableTransferQueue mComplexChannelBuffers; + + public ChannelBufferProcessor() + { + mComplexChannelBuffers = new OverflowableTransferQueue<>(500, 10); + mComplexChannelBuffers.setOverflowListener(new IOverflowListener() + { + @Override + public void sourceOverflow(boolean overflow) + { + broadcastOverflowState(overflow); + } + }); + } + + /** + * Enqueues the polyphase analysis filter's channel output for post channelizer mixing, filtering + * and resampling operations and buffers output for dispatch to downstream processing. + * + * @param channelsBuffer to enqueue for processing + */ + public void process(ComplexBuffer channelsBuffer) + { + mComplexChannelBuffers.offer(channelsBuffer); + } + + @Override + public void run() + { + try + { + + } + catch(Throwable throwable) + { + mLog.error("Error while processing polyphase channel samples", throwable); + } + } + } } diff --git a/src/dsp/filter/channelizer/output/ChannelOutputProcessorFactory.java b/src/dsp/filter/channelizer/output/ChannelOutputProcessorFactory.java new file mode 100644 index 00000000..323fd8a2 --- /dev/null +++ b/src/dsp/filter/channelizer/output/ChannelOutputProcessorFactory.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * 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 + * + ******************************************************************************/ +package dsp.filter.channelizer.output; + +public class ChannelOutputProcessorFactory +{ + /** + * Factory for obtaining polyphase channelizer results processors to perform any post-processing + * that may be required to perform frequency translation and/or resampling of channel results. + */ + public static IPolyphaseChannelOutputProcessor getProcessor(int sampleRate, long frequency) + { + return null; + } +} diff --git a/src/dsp/filter/channelizer/output/IPolyphaseChannelOutputProcessor.java b/src/dsp/filter/channelizer/output/IPolyphaseChannelOutputProcessor.java new file mode 100644 index 00000000..f1d1cd9f --- /dev/null +++ b/src/dsp/filter/channelizer/output/IPolyphaseChannelOutputProcessor.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * 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 + * + ******************************************************************************/ +package dsp.filter.channelizer.output; + +import sample.complex.IComplexSampleListener; + +public interface IPolyphaseChannelOutputProcessor +{ + /** + * Process the channel output channel results from the polyphase channelizer filter + * + * @param channelResults to process + * @param listener to receive the processed channel results + */ + void process(float[] channelResults, IComplexSampleListener listener); + + /** + * Specifies the positive or negative frequency correction that should be applied to samples extracted from + * the polyphase channelizer output results. + * @param frequency + */ + void setFrequencyCorrection(long frequency); +} diff --git a/src/dsp/filter/channelizer/output/SingleChannelOutputProcessor.java b/src/dsp/filter/channelizer/output/SingleChannelOutputProcessor.java new file mode 100644 index 00000000..c0e8eec5 --- /dev/null +++ b/src/dsp/filter/channelizer/output/SingleChannelOutputProcessor.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * 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 + * + ******************************************************************************/ +package dsp.filter.channelizer.output; + +import dsp.mixer.Oscillator; +import sample.complex.Complex; +import sample.complex.IComplexSampleListener; + +import java.util.List; + +public class SingleChannelOutputProcessor implements IPolyphaseChannelOutputProcessor +{ + private int mChannelOffset; + private Oscillator mFrequencyCorrectionMixer; + + /** + * Processor to extract a single channel from a polyphase channelizer and produce an output I/Q complex sample + * from each polyphase channelizer output results array. + * + * @param sampleRate of the output sample stream. + * @param channelIndexes containing a single channel index. + */ + public SingleChannelOutputProcessor(int sampleRate, List channelIndexes) + { + mFrequencyCorrectionMixer = new Oscillator(0, sampleRate); + setPolyphaseChannelIndices(channelIndexes); + } + + /** + * Updates this processor to extract a specified channel index. + * @param indexes containing a single channel index value. + * @throws IllegalArgumentException if the list of indexes does not contain a single channel index. + */ + public void setPolyphaseChannelIndices(List indexes) + { + if(indexes.size() != 1) + { + throw new IllegalArgumentException("Single channel output processor requires a single index to " + + "process - provided indexes " + indexes.toString()); + } + + //Set the channelized output results offset to twice the channel index to account for each channel having + //an I/Q pair + mChannelOffset = indexes.get(0) * 2; + } + + /** + * Extracts the channel from the channel results array, applies frequency translation, and delivers the + * extracted frequency-translated channel sample to the complex sample listener. + * + * @param channelResults to process containing an array of channel I/Q sample pairs (I0,Q0,I1,Q1...In,Qn) + * @param listener to receive the extracted, frequency-translated channel results + */ + @Override + public void process(float[] channelResults, IComplexSampleListener listener) + { + if(channelResults.length >= (mChannelOffset + 1)) + { + //Only perform frequency translation if the frequency correction mixer has a non-zero frequency value + if(mFrequencyCorrectionMixer.isEnabled()) + { + mFrequencyCorrectionMixer.rotate(); + + float i = Complex.multiplyInphase(channelResults[mChannelOffset], channelResults[mChannelOffset + 1], + mFrequencyCorrectionMixer.inphase(), mFrequencyCorrectionMixer.quadrature()); + + float q = Complex.multiplyQuadrature(channelResults[mChannelOffset], channelResults[mChannelOffset + 1], + mFrequencyCorrectionMixer.inphase(), mFrequencyCorrectionMixer.quadrature()); + + listener.receive(i, q); + } + else + { + listener.receive(channelResults[mChannelOffset], channelResults[mChannelOffset + 1]); + } + } + } + + /** + * Specifies a frequency correction/translation value to be applied to channel samples output from the + * polyphase channelizer. + * @param frequency + */ + @Override + public void setFrequencyCorrection(long frequency) + { + mFrequencyCorrectionMixer.setFrequency(frequency); + } +} diff --git a/src/dsp/mixer/Oscillator.java b/src/dsp/mixer/Oscillator.java index 26c6ac6a..70383bec 100644 --- a/src/dsp/mixer/Oscillator.java +++ b/src/dsp/mixer/Oscillator.java @@ -1,135 +1,152 @@ /******************************************************************************* - * SDR Trunk - * Copyright (C) 2014 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 + * 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 + * ******************************************************************************/ package dsp.mixer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sample.complex.Complex; import sample.complex.ComplexBuffer; public class Oscillator { - private final static Logger mLog = LoggerFactory.getLogger( Oscillator.class ); + private final static Logger mLog = LoggerFactory.getLogger(Oscillator.class); - private double mFrequency; - private double mSampleRate; + private boolean mEnabled = true; + private double mFrequency; + private double mSampleRate; - private Complex mAnglePerSample; - private Complex mCurrentAngle = new Complex( 0.0f, -1.0f ); + 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 - * - * @param frequency - positive or negative frequency in hertz - * @param sampleRate - in hertz - */ - public Oscillator( long frequency, int sampleRate ) - { - mSampleRate = (double)sampleRate; - mFrequency = (double)frequency; - - update(); - } - - /** - * Updates the internal values after a frequency or sample rate change - */ - private void update() - { - float anglePerSample = - (float)( 2.0d * Math.PI * mFrequency / mSampleRate ); + /** + * Oscillator 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, int sampleRate) + { + mEnabled = frequency != 0; - mAnglePerSample = Complex.fromAngle( anglePerSample ); - } + mSampleRate = (double)sampleRate; + mFrequency = (double)frequency; - /** - * Sets or changes the frequency of this oscillator - */ - public void setFrequency( long frequency ) - { - mFrequency = (double)frequency; - update(); - } + update(); + } - /** - * Sets or changes the sample rate of this oscillator - */ - public void setSampleRate( int sampleRate ) - { - mSampleRate = (double)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. + */ + public boolean isEnabled() + { + return mEnabled; + } - /** - * Steps the current angle by the angle per sample amount - */ - public void rotate() - { - mCurrentAngle.multiply( mAnglePerSample ); - mCurrentAngle.fastNormalize(); - } + /** + * Updates the internal values after a frequency or sample rate change + */ + private void update() + { + float anglePerSample = + (float)(2.0d * Math.PI * mFrequency / mSampleRate); - public float inphase() - { - return mCurrentAngle.inphase(); - } - - public float quadrature() - { - return mCurrentAngle.quadrature(); - } - - /** - * Get next complex sample - */ - public Complex getComplex() - { - return mCurrentAngle.copy(); - } + mAnglePerSample = Complex.fromAngle(anglePerSample); + } - /** - * Get the next float sample - */ - public float getFloat() - { - return mCurrentAngle.real(); - } + /** + * Sets or changes the frequency of this oscillator + */ + public void setFrequency(long frequency) + { + mEnabled = frequency != 0; + mFrequency = (double)frequency; + update(); + } - /** - * 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]; + /** + * Sets or changes the sample rate of this oscillator + */ + public void setSampleRate(int sampleRate) + { + mSampleRate = (double)sampleRate; + update(); + } - for(int x = 0; x < sampleCount; x++) - { - int index = 2 * x; - buffer[index] = inphase(); - buffer[index + 1] = quadrature(); + /** + * Steps the current angle by the angle per sample amount + */ + public void rotate() + { + mCurrentAngle.multiply(mAnglePerSample); + mCurrentAngle.fastNormalize(); + } - rotate(); - } + public float inphase() + { + return mCurrentAngle.inphase(); + } - return new ComplexBuffer(buffer); - } + public float quadrature() + { + return mCurrentAngle.quadrature(); + } + + /** + * Get next complex sample + */ + public Complex getComplex() + { + 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); + } } diff --git a/src/source/Source.java b/src/source/Source.java index 8df74669..56b61b84 100644 --- a/src/source/Source.java +++ b/src/source/Source.java @@ -1,19 +1,20 @@ /******************************************************************************* - * SDR Trunk - * Copyright (C) 2014 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 + * 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 + * ******************************************************************************/ package source;