diff --git a/src/dsp/filter/channelizer/AbstractComplexPolyphaseChannelizer.java b/src/dsp/filter/channelizer/AbstractComplexPolyphaseChannelizer.java index fc7a0f06..e2b3b74f 100644 --- a/src/dsp/filter/channelizer/AbstractComplexPolyphaseChannelizer.java +++ b/src/dsp/filter/channelizer/AbstractComplexPolyphaseChannelizer.java @@ -120,6 +120,14 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener getSourceEventListener() { diff --git a/src/dsp/filter/channelizer/ChannelizerConfiguration.java b/src/dsp/filter/channelizer/ChannelizerConfiguration.java index be89ac42..b206da9a 100644 --- a/src/dsp/filter/channelizer/ChannelizerConfiguration.java +++ b/src/dsp/filter/channelizer/ChannelizerConfiguration.java @@ -139,7 +139,7 @@ public class ChannelizerConfiguration } /** - * Channelizer input sample rate + * PolyphaseChannelManager input sample rate * @return input sample rate in hertz */ public int getSampleRate() @@ -148,7 +148,7 @@ public class ChannelizerConfiguration } /** - * Channelizer input minimum frequency + * PolyphaseChannelManager input minimum frequency * @return minimum frequency in hertz */ public long getMinimumFrequency() @@ -157,7 +157,7 @@ public class ChannelizerConfiguration } /** - * Channelizer input maximum frequency + * PolyphaseChannelManager input maximum frequency * @return maximum frequency in hertz */ public long getMaximumFrequency() diff --git a/src/dsp/filter/channelizer/ChannelizerViewer.java b/src/dsp/filter/channelizer/ChannelizerViewer.java index 92ba830b..c81b06f2 100644 --- a/src/dsp/filter/channelizer/ChannelizerViewer.java +++ b/src/dsp/filter/channelizer/ChannelizerViewer.java @@ -29,12 +29,16 @@ import sample.Listener; import sample.SampleType; import sample.complex.ComplexBuffer; import settings.SettingsManager; +import source.ISourceEventProcessor; +import source.SourceException; +import source.tuner.TunerChannel; import source.tuner.configuration.TunerConfigurationModel; import source.SourceEvent; import spectrum.DFTProcessor; import spectrum.DFTSize; import spectrum.SpectrumPanel; import spectrum.converter.ComplexDecibelConverter; +import util.ThreadPool; import javax.swing.*; import javax.swing.event.ChangeEvent; @@ -137,7 +141,7 @@ public class ChannelizerViewer extends JFrame { if(mPrimarySpectrumPanel == null) { - mPrimarySpectrumPanel = new ChannelPanel(mSettingsManager, mSampleRate); +// mPrimarySpectrumPanel = new ChannelPanel(mSettingsManager, mSampleRate); mPrimarySpectrumPanel.setPreferredSize(new Dimension(1000, 200)); mPrimarySpectrumPanel.setDFTSize(mMainPanelDFTSize); } @@ -233,9 +237,14 @@ public class ChannelizerViewer extends JFrame { setLayout(new MigLayout("insets 0 0 0 0", "fill", "fill")); + long spectralBandwidth = mChannelCount * CHANNEL_BANDWIDTH; + long halfSpectralBandwidth = spectralBandwidth / 2; + for(int x = 0; x < mChannelCount; x++) { - ChannelPanel channelPanel = new ChannelPanel(mSettingsManager, CHANNEL_BANDWIDTH * 2); + //place the channels left and right of 10.0 MHz + long frequency = 10000000l + ((x * CHANNEL_BANDWIDTH) - halfSpectralBandwidth); + ChannelPanel channelPanel = new ChannelPanel(mSettingsManager, CHANNEL_BANDWIDTH * 2, frequency, CHANNEL_BANDWIDTH); channelPanel.setDFTSize(mChannelPanelDFTSize); if(x % mChannelsPerRow == mChannelsPerRow - 1) @@ -247,6 +256,8 @@ public class ChannelizerViewer extends JFrame add(channelPanel, "grow,push"); } +// channelPanel.getPolyphaseChannelSource() + mChannelDistributor.addListener(x, channelPanel); } } @@ -258,13 +269,14 @@ public class ChannelizerViewer extends JFrame } } - public class ChannelPanel extends JPanel implements Listener + public class ChannelPanel extends JPanel implements Listener, ISourceEventProcessor { + private PolyphaseChannelSource mPolyphaseChannelSource; private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX); private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter(); private SpectrumPanel mSpectrumPanel; - public ChannelPanel(SettingsManager settingsManager, int sampleRate) + public ChannelPanel(SettingsManager settingsManager, int sampleRate, long frequency, int bandwidth) { setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]")); mSpectrumPanel = new SpectrumPanel(settingsManager); @@ -273,6 +285,16 @@ public class ChannelizerViewer extends JFrame mDFTProcessor.addConverter(mComplexDecibelConverter); mDFTProcessor.process(SourceEvent.sampleRateChange(sampleRate)); mComplexDecibelConverter.addListener(mSpectrumPanel); + + TunerChannel tunerChannel = new TunerChannel(frequency, bandwidth); + mPolyphaseChannelSource = new PolyphaseChannelSource(this, tunerChannel); + mPolyphaseChannelSource.setListener(mDFTProcessor); + mPolyphaseChannelSource.start(ThreadPool.SCHEDULED); + } + + public PolyphaseChannelSource getPolyphaseChannelSource() + { + return mPolyphaseChannelSource; } public void setDFTSize(DFTSize dftSize) @@ -285,6 +307,12 @@ public class ChannelizerViewer extends JFrame { mDFTProcessor.receive(complexBuffer); } + + @Override + public void process(SourceEvent event) throws SourceException + { + mLog.debug("Source Event! Add handler support for this to channelizer viewer"); + } } public class SampleGenerationTask implements Runnable @@ -298,8 +326,8 @@ public class ChannelizerViewer extends JFrame public static void main(String[] args) { - int channelCount = 4; - int channelsPerRow = 4; + int channelCount = 160; + int channelsPerRow = 20; int tapsPerChannel = 17; final ChannelizerViewer frame = new ChannelizerViewer(channelCount, channelsPerRow, tapsPerChannel); diff --git a/src/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java b/src/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java index 12ba4832..2eaa64b6 100644 --- a/src/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java +++ b/src/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java @@ -46,7 +46,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann * * @param taps of a low-pass filter designed for the inbound sample rate with a cutoff frequency * equal to the channel bandwidth (sample rate / filters). If you need to synthesize (combine two or more - * filte outputs) a new bandwidth signal from the outputs of this filter, then the filter should be designed + * filter outputs) a new bandwidth signal from the outputs of this filter, then the filter should be designed * as a nyquist filter with -6 dB attenuation at the channel bandwidth cutoff frequency * @param channels - number of filters/channels to output. Since this filter bank oversamples each filter * output, this number must be even (divisible by the oversample rate). diff --git a/src/dsp/filter/channelizer/Channelizer.java b/src/dsp/filter/channelizer/PolyphaseChannelManager.java similarity index 69% rename from src/dsp/filter/channelizer/Channelizer.java rename to src/dsp/filter/channelizer/PolyphaseChannelManager.java index 1acb860d..2b1416c0 100644 --- a/src/dsp/filter/channelizer/Channelizer.java +++ b/src/dsp/filter/channelizer/PolyphaseChannelManager.java @@ -18,6 +18,9 @@ ******************************************************************************/ package dsp.filter.channelizer; +import dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor; +import dsp.filter.channelizer.output.OneChannelOutputProcessor; +import dsp.filter.channelizer.output.TwoChannelOutputProcessor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sample.Listener; @@ -29,34 +32,44 @@ import source.tuner.TunerChannel; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicInteger; -public class Channelizer implements ISourceEventProcessor, Listener +public class PolyphaseChannelManager implements ISourceEventProcessor, Listener { - private final static Logger mLog = LoggerFactory.getLogger(Channelizer.class); + private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class); private static final int CHANNEL_BANDWIDTH = 12500; + private static final int CHANNEL_SAMPLE_RATE = CHANNEL_BANDWIDTH * 2; private static final double CHANNEL_OVERSAMPLING = 2.0; private Tuner mTuner; private List mChannelSources = new CopyOnWriteArrayList<>(); private ChannelizerConfiguration mChannelizerConfiguration; + private ComplexPolyphaseChannelizerM2 mPolyphaseChannelizer; /** - * Provides access to Digital Drop Channel (DDC) sources from the tuner. Incorporates a polyphase channelizer + * Provides access to Digital Drop Channel (DDC) sources from a tuner. Incorporates a polyphase channelizer * with downstream channel and upstream tuner management. * * @param tuner providing broadband sample buffers */ - public Channelizer(Tuner tuner) + public PolyphaseChannelManager(Tuner tuner) { - mTuner = tuner; + if(tuner == null) + { + throw new IllegalArgumentException("Tuner argument cannot be null"); + } int sampleRate = mTuner.getTunerController().getSampleRate(); - if(sampleRate % 25000 != 0) + //Ensure that tuner sample rate is a multiple of channel sample rate since polyphase channelizer is M2 + if(sampleRate % (CHANNEL_SAMPLE_RATE) != 0) { - throw new IllegalArgumentException("Tuner sample rate [" + sampleRate + "] must be a multiple of 25 kHz"); + throw new IllegalArgumentException("Tuner sample rate [" + sampleRate + "] must be a multiple of " + + CHANNEL_SAMPLE_RATE + " Hz"); } + mTuner = tuner; + int channelCount = sampleRate / CHANNEL_BANDWIDTH; long frequency = mTuner.getTunerController().getFrequency(); @@ -75,22 +88,41 @@ public class Channelizer implements ISourceEventProcessor, Listener */ public Source getChannel(TunerChannel tunerChannel) { + PolyphaseChannelSource channelSource = null; + + IPolyphaseChannelOutputProcessor outputProcessor = null; + try { List polyphaseIndexes = mChannelizerConfiguration.getPolyphaseChannelIndexes(tunerChannel); - - + switch(polyphaseIndexes.size()) + { + case 1: + outputProcessor = new OneChannelOutputProcessor(CHANNEL_SAMPLE_RATE, polyphaseIndexes); + break; + case 2: + outputProcessor = new TwoChannelOutputProcessor(CHANNEL_SAMPLE_RATE, polyphaseIndexes); + break; + default: + //TODO: create output processor for greater than 2 input channels + break; + } } 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()) + "]"); + mChannelizerConfiguration.getCenterFrequency() + "] and sample rate [" + + (mChannelizerConfiguration.getChannelCount() * mChannelizerConfiguration.getChannelBandwidth()) + "]"); } - return null; + if(outputProcessor != null) + { + channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, this); + mChannelSources.add(channelSource); + } + + return channelSource; } /** @@ -101,17 +133,17 @@ public class Channelizer implements ISourceEventProcessor, Listener */ private void startChannelSource(PolyphaseChannelSource channelSource) { - if(mChannelSources.isEmpty()) - { - //TODO: start the tuner - } + mPolyphaseChannelizer.addChannel(channelSource); - mChannelSources.add(channelSource); + if(mPolyphaseChannelizer.getRegisteredChannelCount() == 1) + { + mTuner.addListener(mPolyphaseChannelizer); + } } /** * Stops/removes the channel source from receiving channelized sample buffers and deregisters from the tuner - * if this is the last channel being sourced. + * when this is the last channel being sourced. * * @param channelSource to stop */ @@ -119,15 +151,9 @@ public class Channelizer implements ISourceEventProcessor, Listener { mChannelSources.remove(channelSource); - if(mChannelSources.isEmpty()) + if(mPolyphaseChannelizer.getRegisteredChannelCount() == 0) { - //TODO: stop the tuner - } - - if(mTuner != null) - { - //TODO: change the tuner to accept a remove(TunerChannel) method -// mTuner.remove(channel.getTunerChannel()); + mTuner.removeListener(mPolyphaseChannelizer); } } diff --git a/src/dsp/filter/channelizer/PolyphaseChannelSource.java b/src/dsp/filter/channelizer/PolyphaseChannelSource.java index cf6914d5..aa2a4767 100644 --- a/src/dsp/filter/channelizer/PolyphaseChannelSource.java +++ b/src/dsp/filter/channelizer/PolyphaseChannelSource.java @@ -19,6 +19,7 @@ package dsp.filter.channelizer; import channel.heartbeat.Heartbeat; +import dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sample.Listener; @@ -36,6 +37,7 @@ public class PolyphaseChannelSource extends ComplexChannelSource private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class); private TunerChannel mTunerChannel; + private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor; private long mTunerCenterFrequency; private int mUpstreamSampleRate; private int mChannelSampleRate; @@ -49,12 +51,16 @@ public class PolyphaseChannelSource extends ComplexChannelSource * translated and decimated to a single channel. * * @param tunerChannel - requested output tuner channel frequency and bandwidth. + * @param outputProcessor - to process polyphase channelizer channel results into a channel stream + * @param upstreamSourceEventProcessor to handle requests for start/stop sample flow and frequency corrections */ - public PolyphaseChannelSource(ISourceEventProcessor upstreamSourceEventProcessor, TunerChannel tunerChannel) + public PolyphaseChannelSource(TunerChannel tunerChannel, IPolyphaseChannelOutputProcessor outputProcessor, + ISourceEventProcessor upstreamSourceEventProcessor) { super(upstreamSourceEventProcessor); mTunerChannel = tunerChannel; + mPolyphaseChannelOutputProcessor = outputProcessor; } /** diff --git a/src/dsp/filter/channelizer/output/ChannelOutputProcessorFactory.java b/src/dsp/filter/channelizer/output/ChannelOutputProcessorFactory.java deleted file mode 100644 index 4dc141c2..00000000 --- a/src/dsp/filter/channelizer/output/ChannelOutputProcessorFactory.java +++ /dev/null @@ -1,31 +0,0 @@ -/******************************************************************************* - * 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 output 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/TwoChannelOutputProcessor.java b/src/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java index 56d943ad..b242ce29 100644 --- a/src/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java +++ b/src/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java @@ -36,7 +36,9 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor */ public TwoChannelOutputProcessor(int sampleRate, List channelIndexes) { - super(sampleRate); + //Set the frequency correction oscillator to 2 x output sample rate since we'll be correcting the frequency + //after synthesizing both input channels + super(sampleRate * 2); setPolyphaseChannelIndices(channelIndexes); } diff --git a/src/source/tuner/Tuner.java b/src/source/tuner/Tuner.java index 730a0bab..41cf7d26 100644 --- a/src/source/tuner/Tuner.java +++ b/src/source/tuner/Tuner.java @@ -172,6 +172,16 @@ public abstract class Tuner implements ITunerChannelProvider mSampleListeners.remove(listener); } + /** + * Indicates if the listener is currently registered with this tuner + * @param listener of complex buffer samples + * @return true if the listener is registered + */ + public boolean hasListener(Listener listener) + { + return mSampleListeners.contains(listener); + } + /** * Broadcasts the samples to all registered listeners */