From e61c3a891cd4b7dac16d1d945dac471577403bc0 Mon Sep 17 00:00:00 2001 From: "dennis.sheirer" Date: Sun, 26 Oct 2014 10:11:30 +0000 Subject: [PATCH] Memory/CPU performance improvement and Audio/Mixer source restructuring Converted to use float primitives and to keep samples in buffer format until the sample rate is decimated to 48kHz. Added new non-recursive, CIC decimation filter -- untested Updated sound card/mixer input source management. Now testing for mono/stereo support for each card and limiting channel choices accordingly. Separated the mixersource into a ComplexMixer and and SimplexMixerSource. The ComplexMixer is used for sound card based tuners like the funcube. The SimplexMixerSource is used for demodulated sound card inputs. Interfaces for sample handling - standardized on using 'Complex' to indicate I/Q samples and 'Simplex' to indicate demodulated float samples. The processing chain now accepts either a decimated (48k) list of complex samples from a tuner channel source, or a 48k simplex buffer (array of demodulated float samples). Resolved issue with HackRF tuner not applying tuner configuration information. --- src/audio/AudioInverter.java | 14 +- src/audio/AudioOutput.java | 6 +- src/controller/channel/ProcessingChain.java | 172 ++++---- src/decode/Decoder.java | 123 ++++-- src/decode/am/AMDecoder.java | 6 +- src/decode/fleetsync2/Fleetsync2Decoder.java | 13 +- src/decode/ltrnet/LTRNetDecoder.java | 8 +- .../ltrstandard/LTRStandardDecoder.java | 8 +- src/decode/mdc1200/MDCDecoder.java | 10 +- src/decode/mpt1327/MPT1327Decoder.java | 12 +- src/decode/nbfm/NBFMConventionalDecoder.java | 7 +- src/decode/p25/P25Decoder.java | 18 +- src/decode/passport/PassportDecoder.java | 9 +- src/dsp/afc/AutomaticFrequencyControl.java | 12 +- src/dsp/am/AMDemodulator.java | 9 +- src/dsp/filter/ComplexPrimeCICDecimate.java | 368 ++++++++++++++++++ src/dsp/filter/DCRemovalFilter.java | 9 +- src/dsp/filter/DCRemovalFilter2.java | 9 +- src/dsp/filter/DCRemovalFilter3.java | 10 +- src/dsp/filter/FilterFactory.java | 2 - src/dsp/filter/FloatFIRFilter.java | 10 +- src/dsp/filter/FloatHalfBandFilter.java | 10 +- .../filter/FloatHalfBandNoDecimateFilter.java | 10 +- src/dsp/filter/SquaringFilter.java | 5 +- src/dsp/fsk/C4FMDecoder.java | 6 +- src/dsp/fsk/FSK2Decoder.java | 5 +- src/dsp/fsk/LTRFSKDecoder.java | 5 +- src/dsp/nbfm/FMDiscriminator.java | 5 +- src/dsp/nbfm/FilteringNBFMDemodulator.java | 9 +- src/dsp/nbfm/NBFMDemodulator.java | 9 +- src/gui/SDRTrunk.java | 23 +- src/instrument/gui/DecoderViewFrame.java | 2 +- src/instrument/tap/stream/FloatTap.java | 12 +- src/record/wave/FloatWaveRecorder.java | 6 +- .../adapter}/ChannelShortAdapter.java | 8 +- .../ComplexToFloatSampleConverter.java | 3 +- .../FloatArrayToComplexAdapter.java | 3 +- .../adapter}/SampleAdapter.java | 5 +- .../adapter}/ShortAdapter.java | 6 +- src/sample/complex/ComplexBuffer.java | 42 ++ .../complex/ComplexSampleBroadcaster.java | 76 ++++ src/sample/complex/ComplexSampleListener.java | 6 + src/sample/complex/RTLComplexSample.java | 57 --- src/sample/decimator/ComplexDecimator.java | 58 +++ src/sample/{ => decimator}/Decimator.java | 4 +- src/sample/decimator/SimplexDecimator.java | 58 +++ src/sample/simplex/SimplexBuffer.java | 42 ++ .../simplex/SimplexSampleBroadcaster.java | 76 ++++ src/sample/simplex/SimplexSampleListener.java | 6 + src/sample/simplex/SimplexSampleProvider.java | 8 + src/source/FloatArraySource.java | 12 - src/source/SimplexSource.java | 13 + src/source/mixer/ComplexMixer.java | 273 +++++++++++++ src/source/mixer/MixerEditor.java | 29 +- src/source/mixer/MixerManager.java | 204 ++++++++-- ...xerSource.java => SimplexMixerSource.java} | 38 +- src/source/tuner/MixerTuner.java | 70 ++-- src/source/tuner/Tuner.java | 36 +- src/source/tuner/TunerChannelSource.java | 24 +- src/source/tuner/TunerController.java | 4 + src/source/tuner/TunerManager.java | 101 +---- src/source/tuner/fcd/FCDTuner.java | 5 +- src/source/tuner/hackrf/HackRFTuner.java | 9 +- .../tuner/hackrf/HackRFTunerController.java | 37 +- src/source/tuner/rtl/ByteSampleAdapter.java | 7 +- src/source/tuner/rtl/RTL2832Tuner.java | 7 +- .../tuner/rtl/RTL2832TunerController.java | 31 +- src/source/wave/FloatWaveSource.java | 8 +- src/spectrum/ChannelSpectrumPanel.java | 34 +- src/spectrum/DFTProcessor.java | 20 +- src/spectrum/SpectralDisplayPanel.java | 11 +- 71 files changed, 1754 insertions(+), 619 deletions(-) create mode 100644 src/dsp/filter/ComplexPrimeCICDecimate.java rename src/{source/mixer => sample/adapter}/ChannelShortAdapter.java (93%) rename src/sample/{ => adapter}/ComplexToFloatSampleConverter.java (97%) rename src/sample/{ => adapter}/FloatArrayToComplexAdapter.java (97%) rename src/{source/mixer => sample/adapter}/SampleAdapter.java (93%) rename src/{source/mixer => sample/adapter}/ShortAdapter.java (93%) create mode 100644 src/sample/complex/ComplexBuffer.java create mode 100644 src/sample/complex/ComplexSampleBroadcaster.java create mode 100644 src/sample/complex/ComplexSampleListener.java delete mode 100644 src/sample/complex/RTLComplexSample.java create mode 100644 src/sample/decimator/ComplexDecimator.java rename src/sample/{ => decimator}/Decimator.java (97%) create mode 100644 src/sample/decimator/SimplexDecimator.java create mode 100644 src/sample/simplex/SimplexBuffer.java create mode 100644 src/sample/simplex/SimplexSampleBroadcaster.java create mode 100644 src/sample/simplex/SimplexSampleListener.java create mode 100644 src/sample/simplex/SimplexSampleProvider.java delete mode 100644 src/source/FloatArraySource.java create mode 100644 src/source/SimplexSource.java create mode 100644 src/source/mixer/ComplexMixer.java rename src/source/mixer/{MixerSource.java => SimplexMixerSource.java} (86%) diff --git a/src/audio/AudioInverter.java b/src/audio/AudioInverter.java index 26855b5d..a483df07 100644 --- a/src/audio/AudioInverter.java +++ b/src/audio/AudioInverter.java @@ -17,7 +17,7 @@ ******************************************************************************/ package audio; -import sample.Listener; +import sample.simplex.SimplexSampleListener; import util.Oscillator; import dsp.filter.Filters; import dsp.filter.FloatFIRFilter; @@ -26,11 +26,11 @@ import dsp.filter.FloatFIRFilter; * Applies audio inversion (or un-inversion) to a stream of float audio samples * by multiplying each successive sample by either a 1 or a -1 value. */ -public class AudioInverter implements Listener +public class AudioInverter implements SimplexSampleListener { private Oscillator mSineWaveGenerator; private FloatFIRFilter mPostInversionLowPassFilter; - private Listener mListener; + private SimplexSampleListener mListener; public AudioInverter( int inversionFrequency, int sampleRate ) { @@ -47,13 +47,13 @@ public class AudioInverter implements Listener this( frequency.getFrequency(), sampleRate ); } - public void setListener( Listener listener ) + public void setListener( SimplexSampleListener listener ) { mListener = listener; } @Override - public void receive( Float sample ) + public void receive( float sample ) { //Multiply the sample by the folding frequency and then send it to //the low pass filter @@ -68,10 +68,10 @@ public class AudioInverter implements Listener * Simple class to receive the output of the FIR low pass filter and then * send it to the registered listener(s) */ - class FilteredSampleProcessor implements Listener + class FilteredSampleProcessor implements SimplexSampleListener { @Override - public void receive( Float sample ) + public void receive( float sample ) { if( mListener != null ) { diff --git a/src/audio/AudioOutput.java b/src/audio/AudioOutput.java index b5aacb0d..8a50b3f1 100644 --- a/src/audio/AudioOutput.java +++ b/src/audio/AudioOutput.java @@ -27,10 +27,10 @@ import javax.sound.sampled.SourceDataLine; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sample.Listener; +import sample.simplex.SimplexSampleListener; import buffer.FloatSampleBufferAssembler; -public class AudioOutput implements Listener, +public class AudioOutput implements SimplexSampleListener, SquelchListener, AudioTypeListener { @@ -88,7 +88,7 @@ public class AudioOutput implements Listener, } @Override - public void receive( Float sample ) + public void receive( float sample ) { float outputSample = mAdapter.get( sample ); diff --git a/src/controller/channel/ProcessingChain.java b/src/controller/channel/ProcessingChain.java index 9631d871..33330db7 100644 --- a/src/controller/channel/ProcessingChain.java +++ b/src/controller/channel/ProcessingChain.java @@ -21,7 +21,6 @@ import gui.SDRTrunk; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.concurrent.LinkedTransferQueue; import java.util.concurrent.RejectedExecutionException; @@ -40,13 +39,13 @@ import record.wave.FloatWaveRecorder; import sample.Broadcaster; import sample.Listener; import sample.complex.ComplexSample; +import sample.simplex.SimplexBuffer; +import sample.simplex.SimplexSampleListener; import source.ComplexSource; -import source.FloatArraySource; -import source.FloatSource; +import source.SimplexSource; import source.Source; import source.SourceException; import source.config.SourceConfigMixer; -import source.tuner.FrequencyChangeListener; import source.tuner.Tuner; import source.tuner.TunerChannelSource; import alias.AliasList; @@ -86,14 +85,13 @@ public class ProcessingChain implements Listener private ScheduledFuture mProcessorTask; private AliasList mAliasList; - private ComplexListener mComplexListener = new ComplexListener(); - private LinkedTransferQueue> mComplexBuffer = + private ComplexSampleReceiver mComplexReceiver = new ComplexSampleReceiver(); + private LinkedTransferQueue> mComplexQueue = new LinkedTransferQueue>(); - private FloatListener mFloatListener = new FloatListener(); - private FloatArrayListener mFloatArrayListener = new FloatArrayListener(); - private LinkedTransferQueue mFloatBuffer = - new LinkedTransferQueue(); + private SimplexSampleReceiver mSimplexReceiver = new SimplexSampleReceiver(); + private LinkedTransferQueue mSimplexQueue = + new LinkedTransferQueue(); protected Broadcaster mBroadcaster = new Broadcaster(); @@ -118,19 +116,14 @@ public class ProcessingChain implements Listener return mAFC; } - public Listener> getComplexListener() + public Listener> getComplexReceiver() { - return mComplexListener; + return mComplexReceiver; } - public Listener getFloatListener() + public Listener getSimplexReceiver() { - return mFloatListener; - } - - public Listener getFloatArrayListener() - { - return mFloatArrayListener; + return (Listener)mSimplexReceiver; } public String toString() @@ -248,16 +241,12 @@ public class ProcessingChain implements Listener { if( mSource instanceof ComplexSource ) { - ((ComplexSource)mSource).removeListener( this.getComplexListener() ); + ((ComplexSource)mSource).removeListener( this.getComplexReceiver() ); } - else if( mSource instanceof FloatSource ) + else if( mSource instanceof SimplexSource ) { - ((FloatSource)mSource).removeListener( this.getFloatListener() ); - } - else if( mSource instanceof FloatArraySource ) - { - ((FloatArraySource)mSource) - .removeListener( this.getFloatArrayListener() ); + ((SimplexSource)mSource) + .removeListener( this.getSimplexReceiver() ); } if( mProcessorTask != null ) @@ -299,7 +288,7 @@ public class ProcessingChain implements Listener 50, TimeUnit.MILLISECONDS ); ((ComplexSource)mSource) - .setListener( this.getComplexListener() ); + .setListener( this.getComplexReceiver() ); } catch( RejectedExecutionException ree ) { @@ -310,40 +299,18 @@ public class ProcessingChain implements Listener mSource.dispose(); } } - else if( mSource instanceof FloatSource ) + else if( mSource instanceof SimplexSource ) { try { mProcessorTask = mResourceManager.getThreadPoolManager() .schedule( ThreadType.DECODER, - new FloatProcessor(), + new SimplexProcessor(), 50, TimeUnit.MILLISECONDS ); - ((FloatSource)mSource) - .setListener( this.getFloatListener() ); - } - catch( RejectedExecutionException ree ) - { - mLog.error( getLogPrefix() + "ProcessingChain - " - + "error scheduling float sample processing " - + "thread", ree ); - - mSource.dispose(); - } - } - else if( mSource instanceof FloatArraySource ) - { - try - { - mProcessorTask = - mResourceManager.getThreadPoolManager() - .schedule( ThreadType.DECODER, - new FloatProcessor(), - 50, TimeUnit.MILLISECONDS ); - - ((FloatArraySource)mSource) - .setListener( this.getFloatArrayListener() ); + ((SimplexSource)mSource) + .setListener( this.getSimplexReceiver() ); } catch( RejectedExecutionException ree ) { @@ -394,7 +361,7 @@ public class ProcessingChain implements Listener "-" + mChannel.getName(); mAudioOutput = new AudioOutput( threadName ); - addFloatListener( mAudioOutput ); + addSimplexListener( mAudioOutput ); /* Automatic frequency control */ if( mAFC != null ) @@ -415,7 +382,7 @@ public class ProcessingChain implements Listener tcs.addListener( mAFC ); /* Add AFC as listener to the decoder */ - mDecoder.addUnfilteredFloatListener( mAFC ); + mDecoder.addUnfilteredSimplexSampleListener( mAFC ); } } } @@ -525,7 +492,7 @@ public class ProcessingChain implements Listener mLog.info( getLogPrefix() + "- started audio recording [" + recorder.getFileName() + "]" ); - mDecoder.addFloatListener( (FloatWaveRecorder)recorder ); + mDecoder.addSimplexSampleListener( (FloatWaveRecorder)recorder ); } else if( recorder instanceof ComplexWaveRecorder ) { @@ -620,22 +587,32 @@ public class ProcessingChain implements Listener } - public void addFloatListener( Listener listener ) + /** + * Adds the listener to receive demodulated samples from the decoder + */ + public void addSimplexListener( SimplexSampleListener listener ) { if( mDecoder != null ) { - mDecoder.addFloatListener( listener ); + mDecoder.addSimplexSampleListener( listener ); } } - public void removeFloatListener( Listener listener ) + /** + * Removes the listener from receiving demodulated samples from the decoder + */ + public void removeSimplexListener( SimplexSampleListener listener ) { if( mDecoder != null ) { - mDecoder.removeFloatListener( listener ); + mDecoder.removeSimplexListener( listener ); } } + /** + * Adds a listener to receive a copy of complex (I/Q) baseband samples + * received by this processing chain + */ public void addComplexListener( Listener listener ) { if( mDecoder != null ) @@ -649,6 +626,10 @@ public class ProcessingChain implements Listener } } + /** + * Removes the listener from receiving a copy of complex (I/Q) baseband + * samples received by this processing chain + */ public void removeComplexListener( Listener listener ) { if( mDecoder != null ) @@ -662,11 +643,17 @@ public class ProcessingChain implements Listener } } + /** + * Registers the listener to receive messages + */ public void addListener( Listener listener ) { mBroadcaster.addListener( listener ); } + /** + * Registers all listeners in the list to receive messages + */ public void addListeners( List> listeners ) { for( Listener listener: listeners ) @@ -675,43 +662,47 @@ public class ProcessingChain implements Listener } } + /** + * Removes the listener from receiving messages + */ public void removeListener( Listener listener ) { mBroadcaster.removeListener( listener ); } /** - * Runnable float sample processing task. Schedule this task to run 20 - * times a second when we're receiving complex samples from our source. - * Schedule execution via the thread pool manager, obtainable from the - * resource manager, + * Manages the simplex sample queue and distributes samples to the decoder. */ - private class FloatProcessor implements Runnable + private class SimplexProcessor implements Runnable { @Override public void run() { - List samples = new ArrayList(); + List sampleBuffers = new ArrayList(); - mFloatBuffer.drainTo( samples, 48000 ); + mSimplexQueue.drainTo( sampleBuffers, 4 ); - for( Float sample: samples ) + for( SimplexBuffer sampleBuffer: sampleBuffers ) { if( mDecoder != null ) { - mDecoder.getFloatReceiver().receive( sample ); + float[] samples = sampleBuffer.getSamples(); + + for( float sample: samples ) + { + mDecoder.getSimplexReceiver().receive( sample ); + } } + + sampleBuffer.dispose(); } - samples.clear(); + sampleBuffers.clear(); } } /** - * Runnable complex sample processing task. Schedule this task to run 20 - * times a second when we're receiving complex samples from our source. - * Schedule execution via the thread pool manager, obtainable from the - * resource manager, + * Manages the complex sample queue and distributes samples to the decoder. */ private class ComplexProcessor implements Runnable { @@ -721,7 +712,7 @@ public class ProcessingChain implements Listener List> sampleSets = new ArrayList>(); - mComplexBuffer.drainTo( sampleSets, 16 ); + mComplexQueue.drainTo( sampleSets, 16 ); for( List samples: sampleSets ) { @@ -740,30 +731,29 @@ public class ProcessingChain implements Listener } } - public class ComplexListener implements Listener> + /** + * Internal listener to receive complex (I/Q) baseband samples. Places + * received samples into the queue managed by the ComplexProcessor. + */ + public class ComplexSampleReceiver implements Listener> { @Override public void receive( List samples ) { - mComplexBuffer.add( samples ); + mComplexQueue.add( samples ); } } - - public class FloatListener implements Listener + + /** + * Internal listener to receive simplex (ie demodulated) samples. Places + * received samples into the queue managed by the SimplexProcessor + */ + public class SimplexSampleReceiver implements Listener { @Override - public void receive( Float sample ) + public void receive( SimplexBuffer samples ) { - mFloatBuffer.add( sample ); - } - } - - public class FloatArrayListener implements Listener - { - @Override - public void receive( Float[] samples ) - { - mFloatBuffer.addAll( Arrays.asList( samples ) ); + mSimplexQueue.add( samples ); } } } diff --git a/src/decode/Decoder.java b/src/decode/Decoder.java index d8f2d5a4..752c38e1 100644 --- a/src/decode/Decoder.java +++ b/src/decode/Decoder.java @@ -20,11 +20,12 @@ package decode; import java.util.ArrayList; import java.util.List; -import dsp.afc.AutomaticFrequencyControl; import message.Message; import sample.Broadcaster; import sample.Listener; import sample.complex.ComplexSample; +import sample.simplex.SimplexSampleBroadcaster; +import sample.simplex.SimplexSampleListener; import source.Source.SampleType; import eventlog.MessageEventLogger; @@ -32,25 +33,36 @@ public abstract class Decoder implements Listener { private MessageEventLogger mEventLogger; private Broadcaster mMessageBroadcaster = new Broadcaster(); - private Broadcaster mFloatBroadcaster = new Broadcaster(); + private SimplexSampleBroadcaster mSimplexBroadcaster = new SimplexSampleBroadcaster(); private Broadcaster mComplexBroadcaster = new Broadcaster(); protected SampleType mSourceSampleType; protected ArrayList mAuxiliaryDecoders = new ArrayList(); - + + /** + * Abstract decoder class. + * + * @param sampleType - complex (I/Q) or simplex (demodulated) sample type. + * Each decoder instance will be configured to receive either complex or + * simplex samples. When configured to receive complex samples, the + * decoder will implement a demodulator component and feed the output from + * that demodulator back onto itself via the simplex sample interface, so + * that any added auxiliary decoders will then receive the demodulated + * output stream. + */ public Decoder( SampleType sampleType ) { mSourceSampleType = sampleType; } /** - * Returns a float listener interface for connecting this decoder to a - * float stream provider + * Returns a simplex (ie demodulated) sample listener interface for + * connecting this decoder to a simplex sample stream provider */ - public Listener getFloatReceiver() + public SimplexSampleListener getSimplexReceiver() { - return (Listener)mFloatBroadcaster; + return mSimplexBroadcaster; } /** @@ -61,16 +73,12 @@ public abstract class Decoder implements Listener { return (Listener)mComplexBroadcaster; } - + + /** + * Returns the primary decoder type for this decoder + */ public abstract DecoderType getType(); - - public void addAuxiliaryDecoder( Decoder decoder ) - { - mAuxiliaryDecoders.add( decoder ); - mFloatBroadcaster.addListener( decoder.getFloatReceiver() ); - decoder.addMessageListener( this ); - } - + /** * Cleanup method. Invoke this method after stop and before delete. */ @@ -85,30 +93,66 @@ public abstract class Decoder implements Listener mMessageBroadcaster.clear(); mComplexBroadcaster.clear(); - mFloatBroadcaster.clear(); + mSimplexBroadcaster.clear(); + } + + /** + * Adds the auxiliary decoder (piggyback) to this decoder. + * + * Note: we assume that the auxiliary decoder is designed to receive + * demodulated samples, thus we automatically register the aux decoder to + * receive the demodulated sample stream. + * + * Registers this decoder to receive the message output stream from the + * auxiliary decoder, so that those messages can be echoed and included in + * the consolidated message stream to all message listeners registered on + * the primary decoder. + */ + public void addAuxiliaryDecoder( Decoder decoder ) + { + mAuxiliaryDecoders.add( decoder ); + mSimplexBroadcaster.addListener( decoder.getSimplexReceiver() ); + decoder.addMessageListener( this ); } + /** + * Returns a list of all auxiliary decoders that have been added to this + * processing chain + */ public List getAuxiliaryDecoders() { return mAuxiliaryDecoders; } - + + /** + * Sets the MessageEventLogger for this processing chain + */ public void setEventLogger( MessageEventLogger eventLogger ) { mEventLogger = eventLogger; } - + + /** + * Returns the MessageEventLogger for this processing chain + */ public MessageEventLogger getEventLogger() { return mEventLogger; } + /** + * Main receiver method for all demodulators to send their decoded messages + * so that they will be broadcast to all registered listeners + */ @Override public void receive( Message message ) { send( message ); } + /** + * Broadcasts the message to all registered listeners + */ public void send( Message message ) { if( mMessageBroadcaster != null ) @@ -116,34 +160,57 @@ public abstract class Decoder implements Listener mMessageBroadcaster.receive( message ); } } - + + /** + * Adds a listener to receiving decoded messages from all attached decoders + */ public void addMessageListener( Listener listener ) { mMessageBroadcaster.addListener( listener ); } + /** + * Removes the listener from receiving decoded messages from all attached + * decoders + */ public void removeMessageListener( Listener listener ) { mMessageBroadcaster.removeListener( listener ); } - public void addFloatListener( Listener listener ) - { - mFloatBroadcaster.addListener( listener ); - } - - public abstract void addUnfilteredFloatListener( Listener listener ); + /** + * Adds a simplex sample listener to receive unfiltered demodulated samples + */ + public abstract void addUnfilteredSimplexSampleListener( SimplexSampleListener listener ); - public void removeFloatListener( Listener listener ) + /** + * Adds a simplex sample listener to receive demodulated samples + */ + public void addSimplexSampleListener( SimplexSampleListener listener ) { - mFloatBroadcaster.removeListener( listener ); + mSimplexBroadcaster.addListener( listener ); } + /** + * Remove the simplex sample listener from receiving demodulated samples + */ + public void removeSimplexListener( SimplexSampleListener listener ) + { + mSimplexBroadcaster.removeListener( listener ); + } + + /** + * Adds a complex (I/Q) sample listener to receive copy of the inbound + * complex sample stream + */ public void addComplexListener( Listener listener ) { mComplexBroadcaster.addListener( listener ); } + /** + * Removes the complex (I/Q) sample listener from receiving samples + */ public void removeComplexListener( Listener listener ) { mComplexBroadcaster.removeListener( listener ); diff --git a/src/decode/am/AMDecoder.java b/src/decode/am/AMDecoder.java index 113e6d34..cc9ca1d2 100644 --- a/src/decode/am/AMDecoder.java +++ b/src/decode/am/AMDecoder.java @@ -1,6 +1,6 @@ package decode.am; -import sample.Listener; +import sample.simplex.SimplexSampleListener; import source.Source.SampleType; import decode.Decoder; import decode.DecoderType; @@ -61,7 +61,7 @@ public class AMDecoder extends Decoder mDCRemovalFilter.setListener( mLowPassFilter ); - mLowPassFilter.setListener( this.getFloatReceiver() ); + mLowPassFilter.setListener( this.getSimplexReceiver() ); } } @@ -72,7 +72,7 @@ public class AMDecoder extends Decoder } @Override - public void addUnfilteredFloatListener( Listener listener ) + public void addUnfilteredSimplexSampleListener( SimplexSampleListener listener ) { mDemodulator.addListener( listener ); } diff --git a/src/decode/fleetsync2/Fleetsync2Decoder.java b/src/decode/fleetsync2/Fleetsync2Decoder.java index 4760b2d0..3391292a 100644 --- a/src/decode/fleetsync2/Fleetsync2Decoder.java +++ b/src/decode/fleetsync2/Fleetsync2Decoder.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.List; import sample.Listener; +import sample.simplex.SimplexSampleListener; import source.Source.SampleType; import alias.AliasList; import bits.MessageFramer; @@ -75,7 +76,7 @@ public class Fleetsync2Decoder extends Decoder implements Instrumentable mDecimationFilter = new FloatHalfBandFilter( Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 ); - addFloatListener( mDecimationFilter ); + addSimplexSampleListener( mDecimationFilter ); mBandPassFilter = new FloatFIRFilter( Filters.FIRBP_1200FSK_24000FS.getCoefficients(), 1.02 ); @@ -115,9 +116,9 @@ public class Fleetsync2Decoder extends Decoder implements Instrumentable * Returns a float listener interface for connecting this decoder to a * float stream provider */ - public Listener getFloatReceiver() + public SimplexSampleListener getSimplexReceiver() { - return (Listener)mDecimationFilter; + return (SimplexSampleListener)mDecimationFilter; } @Override @@ -147,7 +148,7 @@ public class Fleetsync2Decoder extends Decoder implements Instrumentable { case INSTRUMENT_INPUT: FloatTap inputTap = (FloatTap)tap; - addFloatListener( inputTap ); + addSimplexSampleListener( inputTap ); break; case INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD: FloatTap bpTap = (FloatTap)tap; @@ -171,7 +172,7 @@ public class Fleetsync2Decoder extends Decoder implements Instrumentable { case INSTRUMENT_INPUT: FloatTap inputTap = (FloatTap)tap; - removeFloatListener( inputTap ); + removeSimplexListener( inputTap ); break; case INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD: mBandPassFilter.setListener( mFSKDecoder ); @@ -183,7 +184,7 @@ public class Fleetsync2Decoder extends Decoder implements Instrumentable } @Override - public void addUnfilteredFloatListener( Listener listener ) + public void addUnfilteredSimplexSampleListener( SimplexSampleListener listener ) { //Not implemented } diff --git a/src/decode/ltrnet/LTRNetDecoder.java b/src/decode/ltrnet/LTRNetDecoder.java index 1e550b63..5dede991 100644 --- a/src/decode/ltrnet/LTRNetDecoder.java +++ b/src/decode/ltrnet/LTRNetDecoder.java @@ -24,7 +24,7 @@ import java.util.ArrayList; import java.util.List; import message.MessageDirection; -import sample.Listener; +import sample.simplex.SimplexSampleListener; import source.Source.SampleType; import alias.AliasList; import bits.MessageFramer; @@ -81,7 +81,7 @@ public class LTRNetDecoder extends Decoder implements Instrumentable mDCRemovalFilter = new DCRemovalFilter2( sDC_REMOVAL_RATIO ); mDemodulator.addListener( mDCRemovalFilter ); - mDCRemovalFilter.setListener( this.getFloatReceiver() ); + mDCRemovalFilter.setListener( this.getSimplexReceiver() ); } mLTRFSKDecoder = new LTRFSKDecoder(); @@ -96,7 +96,7 @@ public class LTRNetDecoder extends Decoder implements Instrumentable } else { - this.addFloatListener( mLTRFSKDecoder ); + this.addSimplexSampleListener( mLTRFSKDecoder ); } if( direction == MessageDirection.OSW ) @@ -151,7 +151,7 @@ public class LTRNetDecoder extends Decoder implements Instrumentable } @Override - public void addUnfilteredFloatListener( Listener listener ) + public void addUnfilteredSimplexSampleListener( SimplexSampleListener listener ) { if( mDemodulator != null ) { diff --git a/src/decode/ltrstandard/LTRStandardDecoder.java b/src/decode/ltrstandard/LTRStandardDecoder.java index 251f4ea9..bc1a3eb4 100644 --- a/src/decode/ltrstandard/LTRStandardDecoder.java +++ b/src/decode/ltrstandard/LTRStandardDecoder.java @@ -18,7 +18,7 @@ package decode.ltrstandard; import message.MessageDirection; -import sample.Listener; +import sample.simplex.SimplexSampleListener; import source.Source.SampleType; import alias.AliasList; import bits.MessageFramer; @@ -73,7 +73,7 @@ public class LTRStandardDecoder extends Decoder mDCRemovalFilter = new DCRemovalFilter2( sDC_REMOVAL_RATIO ); mDemodulator.addListener( mDCRemovalFilter ); - mDCRemovalFilter.setListener( this.getFloatReceiver() ); + mDCRemovalFilter.setListener( this.getSimplexReceiver() ); } mLTRFSKDecoder = new LTRFSKDecoder(); @@ -88,7 +88,7 @@ public class LTRStandardDecoder extends Decoder } else { - this.addFloatListener( mLTRFSKDecoder ); + this.addSimplexSampleListener( mLTRFSKDecoder ); } if( direction == MessageDirection.OSW ) @@ -118,7 +118,7 @@ public class LTRStandardDecoder extends Decoder } @Override - public void addUnfilteredFloatListener( Listener listener ) + public void addUnfilteredSimplexSampleListener( SimplexSampleListener listener ) { if( mDemodulator != null ) { diff --git a/src/decode/mdc1200/MDCDecoder.java b/src/decode/mdc1200/MDCDecoder.java index c71c8753..a5c993a6 100644 --- a/src/decode/mdc1200/MDCDecoder.java +++ b/src/decode/mdc1200/MDCDecoder.java @@ -25,7 +25,7 @@ import instrument.tap.stream.FloatTap; import java.util.ArrayList; import java.util.List; -import sample.Listener; +import sample.simplex.SimplexSampleListener; import source.Source.SampleType; import alias.AliasList; import bits.MessageFramer; @@ -79,7 +79,7 @@ public class MDCDecoder extends Decoder implements Instrumentable /* Decimation filter: 48000 / 2 = 24000 */ mDecimationFilter = new FloatHalfBandFilter( Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 ); - addFloatListener( mDecimationFilter ); + addSimplexSampleListener( mDecimationFilter ); /* Bandpass filter - predefined */ mBandPassFilter = new FloatFIRFilter( @@ -154,7 +154,7 @@ public class MDCDecoder extends Decoder implements Instrumentable switch( tap.getName() ) { case INSTRUMENT_INPUT: - addFloatListener( (FloatTap)tap ); + addSimplexSampleListener( (FloatTap)tap ); break; case INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD: /* wire the tap between the bandpass filter and the demod */ @@ -186,7 +186,7 @@ public class MDCDecoder extends Decoder implements Instrumentable switch( tap.getName() ) { case INSTRUMENT_INPUT: - removeFloatListener( (FloatTap)tap ); + removeSimplexListener( (FloatTap)tap ); break; case INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD: mBandPassFilter.setListener( mFSKDecoder ); @@ -202,7 +202,7 @@ public class MDCDecoder extends Decoder implements Instrumentable } @Override - public void addUnfilteredFloatListener( Listener listener ) + public void addUnfilteredSimplexSampleListener( SimplexSampleListener listener ) { //Not implemented } diff --git a/src/decode/mpt1327/MPT1327Decoder.java b/src/decode/mpt1327/MPT1327Decoder.java index 9538da43..e32c5f22 100644 --- a/src/decode/mpt1327/MPT1327Decoder.java +++ b/src/decode/mpt1327/MPT1327Decoder.java @@ -26,7 +26,7 @@ import java.util.ArrayList; import java.util.List; import sample.Broadcaster; -import sample.Listener; +import sample.simplex.SimplexSampleListener; import source.Source.SampleType; import alias.AliasList; import bits.MessageFramer; @@ -106,7 +106,7 @@ public class MPT1327Decoder extends Decoder implements Instrumentable if( mNBFMDemodulator != null ) { mNBFMDemodulator.addListener( mDCRemovalFilter ); - mDCRemovalFilter.setListener( this.getFloatReceiver() ); + mDCRemovalFilter.setListener( this.getSimplexReceiver() ); } } @@ -115,7 +115,7 @@ public class MPT1327Decoder extends Decoder implements Instrumentable /* Decimation filter - 48000 / 2 = 24000 output */ mDecimationFilter = new FloatHalfBandFilter( Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 ); - addFloatListener( mDecimationFilter ); + addSimplexSampleListener( mDecimationFilter ); /* Low pass filter: 2kHz to pass the 1200 & 1800 Hz FSK */ mLowPassFilter = new FloatFIRFilter( @@ -169,7 +169,7 @@ public class MPT1327Decoder extends Decoder implements Instrumentable } @Override - public void addUnfilteredFloatListener( Listener listener ) + public void addUnfilteredSimplexSampleListener( SimplexSampleListener listener ) { if( mNBFMDemodulator != null ) { @@ -256,7 +256,7 @@ public class MPT1327Decoder extends Decoder implements Instrumentable } else { - addFloatListener( inputTap ); + addSimplexSampleListener( inputTap ); } break; case INSTRUMENT_HB1_FILTER_TO_LOW_PASS: @@ -292,7 +292,7 @@ public class MPT1327Decoder extends Decoder implements Instrumentable } else { - removeFloatListener( inputTap ); + removeSimplexListener( inputTap ); } break; case INSTRUMENT_HB1_FILTER_TO_LOW_PASS: diff --git a/src/decode/nbfm/NBFMConventionalDecoder.java b/src/decode/nbfm/NBFMConventionalDecoder.java index c494df23..f6790e19 100644 --- a/src/decode/nbfm/NBFMConventionalDecoder.java +++ b/src/decode/nbfm/NBFMConventionalDecoder.java @@ -17,11 +17,10 @@ ******************************************************************************/ package decode.nbfm; -import sample.Listener; +import sample.simplex.SimplexSampleListener; import source.Source.SampleType; import decode.Decoder; import decode.DecoderType; -import dsp.afc.AutomaticFrequencyControl; import dsp.filter.DCRemovalFilter2; import dsp.nbfm.FilteringNBFMDemodulator; @@ -67,7 +66,7 @@ public class NBFMConventionalDecoder extends Decoder * Route the demodulated, filtered samples back to this class to send * to all registered listeners */ - mDCRemovalFilter.setListener( this.getFloatReceiver() ); + mDCRemovalFilter.setListener( this.getSimplexReceiver() ); } } @@ -78,7 +77,7 @@ public class NBFMConventionalDecoder extends Decoder } @Override - public void addUnfilteredFloatListener( Listener listener ) + public void addUnfilteredSimplexSampleListener( SimplexSampleListener listener ) { if( mDemodulator != null ) { diff --git a/src/decode/p25/P25Decoder.java b/src/decode/p25/P25Decoder.java index 4ba3877c..30131be4 100644 --- a/src/decode/p25/P25Decoder.java +++ b/src/decode/p25/P25Decoder.java @@ -24,12 +24,11 @@ import instrument.tap.stream.FloatTap; import java.util.ArrayList; import java.util.List; -import sample.Listener; +import sample.simplex.SimplexSampleListener; import source.Source.SampleType; import alias.AliasList; import decode.Decoder; import decode.DecoderType; -import dsp.afc.AutomaticFrequencyControl; import dsp.filter.DCRemovalFilter2; import dsp.fsk.C4FMDecoder; import dsp.nbfm.FilteringNBFMDemodulator; @@ -84,12 +83,12 @@ public class P25Decoder extends Decoder implements Instrumentable * Route the demodulated, filtered samples back to this class to send * to all registered listeners */ - mDCRemovalFilter.setListener( this.getFloatReceiver() ); + mDCRemovalFilter.setListener( this.getSimplexReceiver() ); } mC4FMDecoder = new C4FMDecoder(); - addFloatListener( mC4FMDecoder ); + addSimplexSampleListener( mC4FMDecoder ); } @Override @@ -121,8 +120,8 @@ public class P25Decoder extends Decoder implements Instrumentable { case TAP_DEMOD_TO_DECODER: FloatTap demodTap = (FloatTap)tap; - removeFloatListener( mC4FMDecoder ); - addFloatListener( demodTap ); + removeSimplexListener( mC4FMDecoder ); + addSimplexSampleListener( demodTap ); demodTap.setListener( mC4FMDecoder ); break; } @@ -137,17 +136,16 @@ public class P25Decoder extends Decoder implements Instrumentable { case TAP_DEMOD_TO_DECODER: FloatTap demodTap = (FloatTap)tap; - removeFloatListener( demodTap ); + removeSimplexListener( demodTap ); demodTap.removeListener( mC4FMDecoder ); - addFloatListener( mC4FMDecoder ); + addSimplexSampleListener( mC4FMDecoder ); break; } } @Override - public void addUnfilteredFloatListener( Listener listener ) + public void addUnfilteredSimplexSampleListener( SimplexSampleListener listener ) { // TODO Auto-generated method stub - } } diff --git a/src/decode/passport/PassportDecoder.java b/src/decode/passport/PassportDecoder.java index 591f6ca9..2e05ee62 100644 --- a/src/decode/passport/PassportDecoder.java +++ b/src/decode/passport/PassportDecoder.java @@ -23,14 +23,13 @@ import instrument.tap.Tap; import java.util.ArrayList; import java.util.List; -import sample.Listener; +import sample.simplex.SimplexSampleListener; import source.Source.SampleType; import alias.AliasList; import bits.MessageFramer; import bits.SyncPattern; import decode.Decoder; import decode.DecoderType; -import dsp.afc.AutomaticFrequencyControl; import dsp.filter.DCRemovalFilter2; import dsp.fsk.LTRFSKDecoder; import dsp.nbfm.FilteringNBFMDemodulator; @@ -79,7 +78,7 @@ public class PassportDecoder extends Decoder implements Instrumentable */ mDCRemovalFilter = new DCRemovalFilter2( sDC_REMOVAL_RATIO ); mNBFMDemodulator.addListener( mDCRemovalFilter ); - mDCRemovalFilter.setListener( this.getFloatReceiver() ); + mDCRemovalFilter.setListener( this.getSimplexReceiver() ); } mPassportFSKDecoder = @@ -95,7 +94,7 @@ public class PassportDecoder extends Decoder implements Instrumentable } else { - addFloatListener( mPassportFSKDecoder ); + addSimplexSampleListener( mPassportFSKDecoder ); } @@ -142,7 +141,7 @@ public class PassportDecoder extends Decoder implements Instrumentable } @Override - public void addUnfilteredFloatListener( Listener listener ) + public void addUnfilteredSimplexSampleListener( SimplexSampleListener listener ) { if( mNBFMDemodulator != null ) { diff --git a/src/dsp/afc/AutomaticFrequencyControl.java b/src/dsp/afc/AutomaticFrequencyControl.java index fa91b2a8..4912babf 100644 --- a/src/dsp/afc/AutomaticFrequencyControl.java +++ b/src/dsp/afc/AutomaticFrequencyControl.java @@ -1,12 +1,12 @@ package dsp.afc; -import sample.Listener; +import sample.simplex.SimplexSampleListener; import source.tuner.FrequencyChangeEvent; -import source.tuner.FrequencyChangeListener; import source.tuner.FrequencyChangeEvent.Attribute; +import source.tuner.FrequencyChangeListener; import buffer.FloatAveragingBuffer; -public class AutomaticFrequencyControl implements Listener, +public class AutomaticFrequencyControl implements SimplexSampleListener, FrequencyChangeListener { private static float LARGE_ERROR = 10000.0f; @@ -65,11 +65,11 @@ public class AutomaticFrequencyControl implements Listener, } @Override - public void receive( Float sample ) + public void receive( float sample ) { if( mMode == Mode.FAST ) { - float average = mBuffer.get( sample.floatValue() ); + float average = mBuffer.get( sample ); mSampleCounter++; @@ -88,7 +88,7 @@ public class AutomaticFrequencyControl implements Listener, { mSkipCounter = 0; - float average = mBuffer.get( sample.floatValue() ); + float average = mBuffer.get( sample ); mSampleCounter++; diff --git a/src/dsp/am/AMDemodulator.java b/src/dsp/am/AMDemodulator.java index 4dd03175..8be4d8c6 100644 --- a/src/dsp/am/AMDemodulator.java +++ b/src/dsp/am/AMDemodulator.java @@ -1,15 +1,16 @@ package dsp.am; -import sample.Broadcaster; import sample.Listener; import sample.complex.ComplexSample; +import sample.simplex.SimplexSampleBroadcaster; +import sample.simplex.SimplexSampleListener; import dsp.filter.ComplexFIRFilter; import dsp.filter.FilterFactory; import dsp.filter.Window.WindowType; public class AMDemodulator implements Listener { - private Broadcaster mBroadcaster = new Broadcaster(); + private SimplexSampleBroadcaster mBroadcaster = new SimplexSampleBroadcaster(); private ComplexFIRFilter mIQFilter; public AMDemodulator() @@ -34,7 +35,7 @@ public class AMDemodulator implements Listener /** * Adds a listener to receive demodulated samples */ - public void addListener( Listener listener ) + public void addListener( SimplexSampleListener listener ) { mBroadcaster.addListener( listener ); } @@ -42,7 +43,7 @@ public class AMDemodulator implements Listener /** * Removes a listener from receiving demodulated samples */ - public void removeListener( Listener listener ) + public void removeListener( SimplexSampleListener listener ) { mBroadcaster.removeListener( listener ); } diff --git a/src/dsp/filter/ComplexPrimeCICDecimate.java b/src/dsp/filter/ComplexPrimeCICDecimate.java new file mode 100644 index 00000000..b6c08f33 --- /dev/null +++ b/src/dsp/filter/ComplexPrimeCICDecimate.java @@ -0,0 +1,368 @@ +package dsp.filter; + +import java.util.ArrayList; +import java.util.List; + +import sample.Broadcaster; +import sample.Listener; +import sample.complex.ComplexBuffer; +import sample.complex.ComplexSample; +import sample.complex.ComplexSampleListener; +import sample.decimator.ComplexDecimator; +import dsp.filter.Window.WindowType; + +public class ComplexPrimeCICDecimate implements Listener +{ + public static int[] PRIMES = { 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53, + 59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151, + 157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251, + 257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359, + 367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463, + 467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593, + 599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691 }; + + private ArrayList mDecimatingStages = + new ArrayList(); + + private DecimatingStage mFirstDecimatingStage; + + public Broadcaster mBroadcaster = + new Broadcaster(); + + public Output mOutput; + /** + * Non-Recursive Prime-Factor CIC Filter with float sample array inputs and + * decimated, single paired i/q float sample output. + * + * Implements the CIC filter described in Understanding Digital Signal + * Processing, 3e, Lyons, on page 769. This filter is comprised of multiple + * decimating stages each with a prime factor decimation rate. Multiple + * stages are cascaded to achieve the overall decimation rate. + * + * This filter supports a maximum decimation rate of 700. This filter can + * be adapted to higher decimation rates by adding additional prime factors + * to the PRIMES array. + * + * @param decimation - overall decimation rate + * @param order - filter order + */ + public ComplexPrimeCICDecimate( int decimation, int order ) + { + assert( decimation <= 700 ); + + List stageSizes = getPrimeFactors( decimation ); + + for( int x = 0; x < stageSizes.size(); x++ ) + { + DecimatingStage stage = new DecimatingStage( stageSizes.get( x ), order ); + + mDecimatingStages.add( stage ); + + if( x == 0 ) + { + /* Reference to first stage -- will receive all samples */ + mFirstDecimatingStage = stage; + } + else + { + /* Wire the current stage to the previous stage */ + mDecimatingStages.get( x - 1 ).addListener( stage ); + } + } + + mOutput = new Output( 48000, stageSizes.size(), decimation ); + + mDecimatingStages.get( mDecimatingStages.size() - 1 ) + .addListener( mOutput ); + + mOutput.setListener( mBroadcaster ); + } + + public void dispose() + { + for( DecimatingStage stage: mDecimatingStages ) + { + stage.dispose(); + } + + mDecimatingStages.clear(); + + mBroadcaster.dispose(); + + mBroadcaster = null; + } + + /** + * Adds a listener to receive the output of this CIC decimation filter + */ + public void addListener( Listener listener ) + { + mBroadcaster.addListener( listener ); + } + + /** + * Removes listener from output of this CIC decimation filter + */ + public void removeListener( Listener listener ) + { + mBroadcaster.removeListener( listener ); + } + + /** + * Calculates the prime factors of the decimation rate up to a maximum + * decimation rate of 700. If you wish to have decimation rates higher + * than 700, then add additional prime factors to the PRIMES array. + * + * @param decimation - integral decimation rate + * @return - ordered list (smallest to largest) of prime factors + */ + public static List getPrimeFactors( int decimation ) + { + ArrayList stages = new ArrayList(); + + int pointer = 0; + + while( decimation > 0 && pointer < PRIMES.length ) + { + int prime = PRIMES[ pointer ]; + + if( decimation % prime == 0 ) + { + stages.add( prime ); + + decimation /= prime; + } + else + { + pointer++; + } + } + + return stages; + } + + /** + * Primary input method for receiving sample arrays composed as I,Q,I,Q, etc. + */ + @Override + public void receive( ComplexBuffer sampleArray ) + { + float[] samples = sampleArray.getSamples(); + + for( int x = 0; x < samples.length; x += 2 ) + { + mFirstDecimatingStage.receive( samples[ x ], samples[ x + 1 ] ); + } + + samples = null; + } + + /** + * Decimating stage combines multiple CIC stages with a decimator. The + * number of stages is indicated by the order value and the size indicates + * the decimation rate of this stage. + */ + public class DecimatingStage implements ComplexSampleListener + { + private ArrayList mStages = new ArrayList(); + private Stage mFirstStage; + private ComplexDecimator mDecimator; + + public DecimatingStage( int size, int order ) + { + for( int x = 0; x < order; x++ ) + { + Stage stage = new Stage( size ); + + mStages.add( stage ); + + if( x == 0 ) + { + mFirstStage = stage; + } + else + { + mStages.get( x - 1 ).addListener( stage ); + } + } + + mDecimator = new ComplexDecimator( size ); + + mStages.get( mStages.size() - 1 ).addListener( mDecimator ); + } + + public void dispose() + { + for( Stage stage: mStages ) + { + stage.dispose(); + } + + mStages.clear(); + mDecimator.dispose(); + mDecimator = null; + mFirstStage = null; + mStages = null; + } + + @Override + public void receive( float i, float q ) + { + mFirstStage.receive( i, q ); + } + + public void addListener( ComplexSampleListener listener ) + { + mDecimator.setListener( listener ); + } + } + + /** + * Single non-decimating CIC stage component. Uses a circular buffer + * internally to implement the stage so that stage size has essentially + * no impact on the performance of the stage + */ + public class Stage implements ComplexSampleListener + { + private ComplexSampleListener mListener; + + private float[] mISamples; + private float[] mQSamples; + + private float mISum; + private float mQSum; + + private int mSamplePointer; + private int mSize; + + public Stage( int size ) + { + mSize = size; + mISamples = new float[ size ]; + mQSamples = new float[ size ]; + } + + public void dispose() + { + mListener = null; + mISamples = null; + mQSamples = null; + } + + public void receive( float i, float q ) + { + /* Subtract the oldest sample and add back in the newest */ + mISum = mISum - mISamples[ mSamplePointer ] + i; + mQSum = mQSum - mISamples[ mSamplePointer ] + q; + + /* Overwrite the oldest sample with the newest */ + mISamples[ mSamplePointer ] = i; + mQSamples[ mSamplePointer ] = q; + + mSamplePointer++; + + if( mSamplePointer >= mSize ) + { + mSamplePointer = 0; + } + + if( mListener != null ) + { + mListener.receive( mISum, mQSum ); + } + } + + public void addListener( ComplexSampleListener listener ) + { + mListener = listener; + } + } + + /** + * Output adapter - applies gain correction, applies cleanup filter, casts + * double-valued samples into float-valued complex sample and sends the + * result to the registered listener. + */ + public class Output implements ComplexSampleListener + { + private int mStageCount; + private int mDecimation; + private double mGain = 1.0d; + private ComplexFilter mCleanupFilter; + + public Output( int outputSampleRate, int stageCount, int decimation ) + { + mStageCount = stageCount; + mDecimation = decimation; + mCleanupFilter = new ComplexFIRFilter( FilterFactory + .getCICCleanupFilter( outputSampleRate, + stageCount, + WindowType.BLACKMAN ), 1.0d ); + + setGain(); + } + + /** + * Receiver method for the output adapter to receive a filtered, + * decimated sample, apply gain correction, apply cleanup filtering + * and output the sample values as a complex sample. + */ + @Override + public void receive( float left, float right ) + { + mCleanupFilter.receive( + new ComplexSample( (float)( left * mGain ), + (float)( right * mGain ) ) ); + } + + /** + * Sets a new decimation rate + */ + public void setDecimationRate( int rate ) + { + mDecimation = rate; + setGain(); + } + + /** + * Adds a listener to receive output samples + */ + public void setListener( Listener listener ) + { + mCleanupFilter.setListener( listener ); + } + + /** + * Removes the listener from receiving output samples + */ + public void removeListener( Listener listener ) + { + mCleanupFilter.setListener( null ); + } + + public Listener getListener() + { + return mCleanupFilter.getListener(); + } + + /** + * Adjusts the gain correction factor for output samples. + * + * The CIC decimation filter induces significant gain as a factor of + * delay, decimation rate and stage count. So, we apply a gain reduction + * to offset the added gain estimate: + * + * gain = ( decimation * delay ) to power of stageCount + * + * Delay is ONE for the Comb used in this filter. + */ + private void setGain() + { + /* decimation is NOT multiplied by delay since delay is 1 */ + double gain = Math.pow( (double)mDecimation, (double)mStageCount ); + + mGain = 10.0d / gain; + } + } + +} diff --git a/src/dsp/filter/DCRemovalFilter.java b/src/dsp/filter/DCRemovalFilter.java index ed61a29a..2ea181b0 100644 --- a/src/dsp/filter/DCRemovalFilter.java +++ b/src/dsp/filter/DCRemovalFilter.java @@ -18,16 +18,17 @@ package dsp.filter; import sample.Listener; +import sample.simplex.SimplexSampleListener; /** * Implements the real-time general DC removal algorithm described on page * 762, figure 13-62d, in Understanding Digital Signal Processing 3e, Lyons. */ -public class DCRemovalFilter implements Listener +public class DCRemovalFilter implements SimplexSampleListener { private double mAlpha; private float mPrevious; - private Listener mListener; + private SimplexSampleListener mListener; public DCRemovalFilter( double alpha ) { @@ -40,7 +41,7 @@ public class DCRemovalFilter implements Listener } @Override - public void receive( Float sample ) + public void receive( float sample ) { send( (float)( sample + ( mPrevious * mAlpha ) - mPrevious ) ); @@ -58,7 +59,7 @@ public class DCRemovalFilter implements Listener } } - public void setListener( Listener listener ) + public void setListener( SimplexSampleListener listener ) { mListener = listener; } diff --git a/src/dsp/filter/DCRemovalFilter2.java b/src/dsp/filter/DCRemovalFilter2.java index eb89e565..fa1fe6a5 100644 --- a/src/dsp/filter/DCRemovalFilter2.java +++ b/src/dsp/filter/DCRemovalFilter2.java @@ -18,12 +18,13 @@ package dsp.filter; import sample.Listener; +import sample.simplex.SimplexSampleListener; -public class DCRemovalFilter2 implements Listener +public class DCRemovalFilter2 implements SimplexSampleListener { private double mAverage; private double mRatio; - private Listener mListener; + private SimplexSampleListener mListener; public DCRemovalFilter2( double ratio ) { @@ -31,7 +32,7 @@ public class DCRemovalFilter2 implements Listener } @Override - public void receive( Float sample ) + public void receive( float sample ) { mAverage += mRatio * ( (double)sample - mAverage ); @@ -49,7 +50,7 @@ public class DCRemovalFilter2 implements Listener } } - public void setListener( Listener listener ) + public void setListener( SimplexSampleListener listener ) { mListener = listener; } diff --git a/src/dsp/filter/DCRemovalFilter3.java b/src/dsp/filter/DCRemovalFilter3.java index b584abfb..af119490 100644 --- a/src/dsp/filter/DCRemovalFilter3.java +++ b/src/dsp/filter/DCRemovalFilter3.java @@ -1,14 +1,14 @@ package dsp.filter; -import sample.Listener; +import sample.simplex.SimplexSampleListener; -public class DCRemovalFilter3 implements Listener +public class DCRemovalFilter3 implements SimplexSampleListener { private float mAlpha; private float mPreviousInput = 0.0f; private float mPreviousOutput = 0.0f; - private Listener mListener; + private SimplexSampleListener mListener; /** * IIR single-pole DC removal filter, as described by J M de Freitas in @@ -24,7 +24,7 @@ public class DCRemovalFilter3 implements Listener mAlpha = alpha; } @Override - public void receive( Float currentInput ) + public void receive( float currentInput ) { float currentOutput = ( currentInput - mPreviousInput ) + ( mAlpha * mPreviousOutput ); @@ -38,7 +38,7 @@ public class DCRemovalFilter3 implements Listener mPreviousOutput = currentOutput; } - public void setListener( Listener listener ) + public void setListener( SimplexSampleListener listener ) { mListener = listener; } diff --git a/src/dsp/filter/FilterFactory.java b/src/dsp/filter/FilterFactory.java index fd3099b6..8d04de76 100644 --- a/src/dsp/filter/FilterFactory.java +++ b/src/dsp/filter/FilterFactory.java @@ -17,8 +17,6 @@ ******************************************************************************/ package dsp.filter; -import gui.SDRTrunk; - import java.util.Arrays; import java.util.Set; import java.util.TreeSet; diff --git a/src/dsp/filter/FloatFIRFilter.java b/src/dsp/filter/FloatFIRFilter.java index cb6ee841..edaa4711 100644 --- a/src/dsp/filter/FloatFIRFilter.java +++ b/src/dsp/filter/FloatFIRFilter.java @@ -19,11 +19,11 @@ package dsp.filter; import java.util.ArrayList; -import sample.Listener; +import sample.simplex.SimplexSampleListener; -public class FloatFIRFilter implements Listener +public class FloatFIRFilter implements SimplexSampleListener { - private Listener mListener; + private SimplexSampleListener mListener; private ArrayList mBuffer; private int mBufferSize = 1; //Temporary initial value private int mBufferPointer = 0; @@ -61,12 +61,12 @@ public class FloatFIRFilter implements Listener return mCoefficients.length; } - public void setListener( Listener listener ) + public void setListener( SimplexSampleListener listener ) { mListener = listener; } - public void receive( Float newSample ) + public void receive( float newSample ) { send( get( newSample ) ); } diff --git a/src/dsp/filter/FloatHalfBandFilter.java b/src/dsp/filter/FloatHalfBandFilter.java index b832c35a..e0816ba8 100644 --- a/src/dsp/filter/FloatHalfBandFilter.java +++ b/src/dsp/filter/FloatHalfBandFilter.java @@ -19,11 +19,11 @@ package dsp.filter; import java.util.ArrayList; -import sample.Listener; +import sample.simplex.SimplexSampleListener; -public class FloatHalfBandFilter implements Listener +public class FloatHalfBandFilter implements SimplexSampleListener { - private Listener mListener; + private SimplexSampleListener mListener; private ArrayList mBuffer; private int mBufferSize = 1; //Temporary initial value private int mBufferPointer = 0; @@ -75,7 +75,7 @@ public class FloatHalfBandFilter implements Listener * Calculate the filtered value by applying the coefficients against * the complex samples in mBuffer */ - public void receive( Float newSample ) + public void receive( float newSample ) { //Add the new sample to the buffer mBuffer.set( mBufferPointer, newSample ); @@ -188,7 +188,7 @@ public class FloatHalfBandFilter implements Listener /** * Registers a listener for filtered samples */ - public void setListener( Listener listener ) + public void setListener( SimplexSampleListener listener ) { mListener = listener; } diff --git a/src/dsp/filter/FloatHalfBandNoDecimateFilter.java b/src/dsp/filter/FloatHalfBandNoDecimateFilter.java index 9529bfd8..aa180c2e 100644 --- a/src/dsp/filter/FloatHalfBandNoDecimateFilter.java +++ b/src/dsp/filter/FloatHalfBandNoDecimateFilter.java @@ -19,11 +19,11 @@ package dsp.filter; import java.util.ArrayList; -import sample.Listener; +import sample.simplex.SimplexSampleListener; -public class FloatHalfBandNoDecimateFilter implements Listener +public class FloatHalfBandNoDecimateFilter implements SimplexSampleListener { - private Listener mListener; + private SimplexSampleListener mListener; private ArrayList mBuffer; private int mBufferSize = 1; //Temporary initial value private int mBufferPointer = 0; @@ -73,7 +73,7 @@ public class FloatHalfBandNoDecimateFilter implements Listener * Calculate the filtered value by applying the coefficients against * the complex samples in mBuffer */ - public void receive( Float newSample ) + public void receive( float newSample ) { //Add the new sample to the buffer mBuffer.set( mBufferPointer, newSample ); @@ -178,7 +178,7 @@ public class FloatHalfBandNoDecimateFilter implements Listener /** * Registers a listener for filtered samples */ - public void setListener( Listener listener ) + public void setListener( SimplexSampleListener listener ) { mListener = listener; } diff --git a/src/dsp/filter/SquaringFilter.java b/src/dsp/filter/SquaringFilter.java index 7b380845..0d79615c 100644 --- a/src/dsp/filter/SquaringFilter.java +++ b/src/dsp/filter/SquaringFilter.java @@ -18,8 +18,9 @@ package dsp.filter; import sample.Listener; +import sample.simplex.SimplexSampleListener; -public class SquaringFilter implements Listener +public class SquaringFilter implements SimplexSampleListener { private Listener mListener; @@ -31,7 +32,7 @@ public class SquaringFilter implements Listener } @Override - public void receive( Float sample ) + public void receive( float sample ) { if( mListener != null ) { diff --git a/src/dsp/fsk/C4FMDecoder.java b/src/dsp/fsk/C4FMDecoder.java index 1bbd4974..ca21f638 100644 --- a/src/dsp/fsk/C4FMDecoder.java +++ b/src/dsp/fsk/C4FMDecoder.java @@ -7,9 +7,9 @@ import instrument.tap.stream.FloatTap; import java.util.ArrayList; import java.util.List; -import sample.Listener; +import sample.simplex.SimplexSampleListener; -public class C4FMDecoder implements Listener, Instrumentable +public class C4FMDecoder implements SimplexSampleListener, Instrumentable { private final String TAP_C4FM_INPUT = "C4FM Input"; private ArrayList mTaps; @@ -56,7 +56,7 @@ public class C4FMDecoder implements Listener, Instrumentable } @Override - public void receive( Float t ) + public void receive( float t ) { if( mInputTap != null ) { diff --git a/src/dsp/fsk/FSK2Decoder.java b/src/dsp/fsk/FSK2Decoder.java index a4ad9f93..c8e66cfa 100644 --- a/src/dsp/fsk/FSK2Decoder.java +++ b/src/dsp/fsk/FSK2Decoder.java @@ -26,6 +26,7 @@ import java.util.BitSet; import java.util.List; import sample.Listener; +import sample.simplex.SimplexSampleListener; import buffer.BooleanAveragingBuffer; import dsp.fsk.SymbolEvent.Shift; @@ -49,7 +50,7 @@ import dsp.fsk.SymbolEvent.Shift; * Implements instrumentable interface, so that slice events can be received * externally to analyze decoder performance. */ -public class FSK2Decoder implements Instrumentable, Listener +public class FSK2Decoder implements Instrumentable, SimplexSampleListener { public enum Output{ NORMAL, INVERTED }; @@ -93,7 +94,7 @@ public class FSK2Decoder implements Instrumentable, Listener * Primary sample input */ @Override - public void receive( Float floatSample ) + public void receive( float floatSample ) { /* Square the sample. Greater than zero is a 1 (true) and less than * zero is a 0 (false) */ diff --git a/src/dsp/fsk/LTRFSKDecoder.java b/src/dsp/fsk/LTRFSKDecoder.java index 04d7fce9..21ef2b16 100644 --- a/src/dsp/fsk/LTRFSKDecoder.java +++ b/src/dsp/fsk/LTRFSKDecoder.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.List; import sample.Listener; +import sample.simplex.SimplexSampleListener; import dsp.Slicer; import dsp.Slicer.Output; import dsp.filter.DCRemovalFilter3; @@ -36,7 +37,7 @@ import dsp.filter.FloatHalfBandNoDecimateFilter; import dsp.filter.LTRPulseShapingFilter; import dsp.filter.SquaringFilter; -public class LTRFSKDecoder implements Listener, Instrumentable +public class LTRFSKDecoder implements SimplexSampleListener, Instrumentable { private FloatHalfBandFilter mHBFilter1; private FloatHalfBandFilter mHBFilter2; @@ -104,7 +105,7 @@ public class LTRFSKDecoder implements Listener, Instrumentable } @Override - public void receive( Float sample ) + public void receive( float sample ) { mHBFilter1.receive( sample ); } diff --git a/src/dsp/nbfm/FMDiscriminator.java b/src/dsp/nbfm/FMDiscriminator.java index 3029a4b5..3c8b9a9d 100644 --- a/src/dsp/nbfm/FMDiscriminator.java +++ b/src/dsp/nbfm/FMDiscriminator.java @@ -19,10 +19,11 @@ package dsp.nbfm; import sample.Listener; import sample.complex.ComplexSample; +import sample.simplex.SimplexSampleListener; public class FMDiscriminator implements Listener { - private Listener mListener; + private SimplexSampleListener mListener; private ComplexSample mPreviousSample = new ComplexSample( 0.0f, 0.0f ); private double mGain; @@ -93,7 +94,7 @@ public class FMDiscriminator implements Listener mPreviousSample = currentSample; } - public void setListener( Listener listener ) + public void setListener( SimplexSampleListener listener ) { mListener = listener; } diff --git a/src/dsp/nbfm/FilteringNBFMDemodulator.java b/src/dsp/nbfm/FilteringNBFMDemodulator.java index 267dcee5..f9815c08 100644 --- a/src/dsp/nbfm/FilteringNBFMDemodulator.java +++ b/src/dsp/nbfm/FilteringNBFMDemodulator.java @@ -17,9 +17,10 @@ ******************************************************************************/ package dsp.nbfm; -import sample.Broadcaster; import sample.Listener; import sample.complex.ComplexSample; +import sample.simplex.SimplexSampleBroadcaster; +import sample.simplex.SimplexSampleListener; import dsp.filter.ComplexFIRFilter; import dsp.filter.FilterFactory; import dsp.filter.FloatFIRFilter; @@ -27,7 +28,7 @@ import dsp.filter.Window.WindowType; public class FilteringNBFMDemodulator implements Listener { - private Broadcaster mBroadcaster = new Broadcaster(); + private SimplexSampleBroadcaster mBroadcaster = new SimplexSampleBroadcaster(); private ComplexFIRFilter mIQFilter; private FloatFIRFilter mAudioFilter; private FMDiscriminator mDiscriminator; @@ -77,7 +78,7 @@ public class FilteringNBFMDemodulator implements Listener /** * Adds a listener to receive demodulated samples */ - public void addListener( Listener listener ) + public void addListener( SimplexSampleListener listener ) { mBroadcaster.addListener( listener ); } @@ -85,7 +86,7 @@ public class FilteringNBFMDemodulator implements Listener /** * Removes a listener from receiving demodulated samples */ - public void removeListener( Listener listener ) + public void removeListener( SimplexSampleListener listener ) { mBroadcaster.removeListener( listener ); } diff --git a/src/dsp/nbfm/NBFMDemodulator.java b/src/dsp/nbfm/NBFMDemodulator.java index 021e5824..c763e620 100644 --- a/src/dsp/nbfm/NBFMDemodulator.java +++ b/src/dsp/nbfm/NBFMDemodulator.java @@ -17,16 +17,17 @@ ******************************************************************************/ package dsp.nbfm; -import sample.Broadcaster; import sample.Listener; import sample.complex.ComplexSample; +import sample.simplex.SimplexSampleBroadcaster; +import sample.simplex.SimplexSampleListener; import dsp.filter.ComplexFIRFilter; import dsp.filter.FilterFactory; import dsp.filter.Window.WindowType; public class NBFMDemodulator implements Listener { - private Broadcaster mBroadcaster = new Broadcaster(); + private SimplexSampleBroadcaster mBroadcaster = new SimplexSampleBroadcaster(); private ComplexFIRFilter mIQFilter; private FMDiscriminator mDiscriminator; @@ -77,7 +78,7 @@ public class NBFMDemodulator implements Listener /** * Adds a listener to receive demodulated samples */ - public void addListener( Listener listener ) + public void addListener( SimplexSampleListener listener ) { mBroadcaster.addListener( listener ); } @@ -85,7 +86,7 @@ public class NBFMDemodulator implements Listener /** * Removes a listener from receiving demodulated samples */ - public void removeListener( Listener listener ) + public void removeListener( SimplexSampleListener listener ) { mBroadcaster.removeListener( listener ); } diff --git a/src/gui/SDRTrunk.java b/src/gui/SDRTrunk.java index 5c99989c..7a5b1cb7 100644 --- a/src/gui/SDRTrunk.java +++ b/src/gui/SDRTrunk.java @@ -42,6 +42,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import properties.SystemProperties; +import source.tuner.Tuner; import source.tuner.TunerSelectionListener; import spectrum.SpectralDisplayPanel; import util.TimeStamp; @@ -144,15 +145,31 @@ public class SDRTrunk //init() the controller to load tuners and playlists mControllerPanel.getController().init(); - //Fire first tuner selection so it is displayed in the spectrum/waterfall - mControllerPanel.getController().fireFirstTunerSelected(); - /** * Setup main JFrame window */ mMainGui.setTitle( "SDRTrunk" ); mMainGui.setBounds( 100, 100, 1280, 800 ); mMainGui.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); + + mControllerPanel.getController().addListener( new TunerSelectionListener() + { + @Override + public void tunerSelected( Tuner tuner ) + { + if( tuner != null ) + { + mMainGui.setTitle( "SDRTrunk - " + tuner.getName() ); + } + else + { + mMainGui.setTitle( "SDRTrunk" ); + } + } + } ); + + //Fire first tuner selection so it is displayed in the spectrum/waterfall + mControllerPanel.getController().fireFirstTunerSelected(); mSpectralPanel.setPreferredSize( new Dimension( 1280, 400 ) ); diff --git a/src/instrument/gui/DecoderViewFrame.java b/src/instrument/gui/DecoderViewFrame.java index 8dc66e52..98cd1575 100644 --- a/src/instrument/gui/DecoderViewFrame.java +++ b/src/instrument/gui/DecoderViewFrame.java @@ -80,7 +80,7 @@ public class DecoderViewFrame extends JInternalFrame else if( source.getSampleType() == SampleType.FLOAT ) { FloatWaveSource fws = (FloatWaveSource)mWaveSource; - fws.setListener( mDecoder.getFloatReceiver() ); + fws.setListener( mDecoder.getSimplexReceiver() ); } mDecoder.addMessageListener( this ); diff --git a/src/instrument/tap/stream/FloatTap.java b/src/instrument/tap/stream/FloatTap.java index 15540dbf..4253c09c 100644 --- a/src/instrument/tap/stream/FloatTap.java +++ b/src/instrument/tap/stream/FloatTap.java @@ -19,11 +19,11 @@ package instrument.tap.stream; import instrument.tap.TapListener; import instrument.tap.TapType; -import sample.Listener; +import sample.simplex.SimplexSampleListener; -public class FloatTap extends StreamTap implements Listener +public class FloatTap extends StreamTap implements SimplexSampleListener { - private Listener mListener; + private SimplexSampleListener mListener; public FloatTap( String name, int delay, @@ -33,7 +33,7 @@ public class FloatTap extends StreamTap implements Listener } @Override - public void receive( Float sample ) + public void receive( float sample ) { if( mListener != null ) { @@ -46,12 +46,12 @@ public class FloatTap extends StreamTap implements Listener } } - public void setListener( Listener listener ) + public void setListener( SimplexSampleListener listener ) { mListener = listener; } - public void removeListener( Listener listener ) + public void removeListener( SimplexSampleListener listener ) { mListener = null; } diff --git a/src/record/wave/FloatWaveRecorder.java b/src/record/wave/FloatWaveRecorder.java index 126d85c3..1a789f13 100644 --- a/src/record/wave/FloatWaveRecorder.java +++ b/src/record/wave/FloatWaveRecorder.java @@ -28,14 +28,14 @@ import org.slf4j.LoggerFactory; import record.Recorder; import record.RecorderType; -import sample.Listener; +import sample.simplex.SimplexSampleListener; import util.waveaudio.WaveWriter; import buffer.FloatSampleBufferAssembler; /** * Threaded WAVE audio recorder for recording mono float samples to a wave file */ -public class FloatWaveRecorder extends Recorder implements Listener +public class FloatWaveRecorder extends Recorder implements SimplexSampleListener { private final static Logger mLog = LoggerFactory.getLogger( FloatWaveRecorder.class ); @@ -129,7 +129,7 @@ public class FloatWaveRecorder extends Recorder implements Listener } @Override - public void receive( Float sample ) + public void receive( float sample ) { mCurrentBuffer.put( sample ); diff --git a/src/source/mixer/ChannelShortAdapter.java b/src/sample/adapter/ChannelShortAdapter.java similarity index 93% rename from src/source/mixer/ChannelShortAdapter.java rename to src/sample/adapter/ChannelShortAdapter.java index 0b6086a5..e88f6fa6 100644 --- a/src/source/mixer/ChannelShortAdapter.java +++ b/src/sample/adapter/ChannelShortAdapter.java @@ -15,12 +15,12 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see ******************************************************************************/ -package source.mixer; +package sample.adapter; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import source.Source.SampleType; +import source.mixer.MixerChannel; /** * Converts 16-bit little endian byte data into an array of floats @@ -39,9 +39,9 @@ public class ChannelShortAdapter extends SampleAdapter } @Override - public Float[] convert( byte[] samples ) + public float[] convert( byte[] samples ) { - Float[] processed = new Float[ samples.length / 4 ]; + float[] processed = new float[ samples.length / 4 ]; int pointer = 0; diff --git a/src/sample/ComplexToFloatSampleConverter.java b/src/sample/adapter/ComplexToFloatSampleConverter.java similarity index 97% rename from src/sample/ComplexToFloatSampleConverter.java rename to src/sample/adapter/ComplexToFloatSampleConverter.java index 2dba6104..8416fc88 100644 --- a/src/sample/ComplexToFloatSampleConverter.java +++ b/src/sample/adapter/ComplexToFloatSampleConverter.java @@ -15,10 +15,11 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see ******************************************************************************/ -package sample; +package sample.adapter; import java.util.List; +import sample.Listener; import sample.complex.ComplexSample; import source.mixer.MixerChannel; diff --git a/src/sample/FloatArrayToComplexAdapter.java b/src/sample/adapter/FloatArrayToComplexAdapter.java similarity index 97% rename from src/sample/FloatArrayToComplexAdapter.java rename to src/sample/adapter/FloatArrayToComplexAdapter.java index e55ef201..36e520f5 100644 --- a/src/sample/FloatArrayToComplexAdapter.java +++ b/src/sample/adapter/FloatArrayToComplexAdapter.java @@ -15,8 +15,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see ******************************************************************************/ -package sample; +package sample.adapter; +import sample.Listener; import sample.complex.ComplexSample; public class FloatArrayToComplexAdapter implements Listener diff --git a/src/source/mixer/SampleAdapter.java b/src/sample/adapter/SampleAdapter.java similarity index 93% rename from src/source/mixer/SampleAdapter.java rename to src/sample/adapter/SampleAdapter.java index d4ae6567..a5e3ef50 100644 --- a/src/source/mixer/SampleAdapter.java +++ b/src/sample/adapter/SampleAdapter.java @@ -15,8 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see ******************************************************************************/ -package source.mixer; - +package sample.adapter; /* * Adapter to convert byte data received from a data stream into float[] @@ -24,5 +23,5 @@ package source.mixer; */ public abstract class SampleAdapter { - public abstract Float[] convert( byte[] samples ); + public abstract float[] convert( byte[] samples ); } diff --git a/src/source/mixer/ShortAdapter.java b/src/sample/adapter/ShortAdapter.java similarity index 93% rename from src/source/mixer/ShortAdapter.java rename to src/sample/adapter/ShortAdapter.java index 6497bb11..bf3365e9 100644 --- a/src/source/mixer/ShortAdapter.java +++ b/src/sample/adapter/ShortAdapter.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see ******************************************************************************/ -package source.mixer; +package sample.adapter; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -28,9 +28,9 @@ public class ShortAdapter extends SampleAdapter private ByteOrder mByteOrder = ByteOrder.LITTLE_ENDIAN; @Override - public Float[] convert( byte[] samples ) + public float[] convert( byte[] samples ) { - Float[] processed = new Float[ samples.length / 2 ]; + float[] processed = new float[ samples.length / 2 ]; int pointer = 0; diff --git a/src/sample/complex/ComplexBuffer.java b/src/sample/complex/ComplexBuffer.java new file mode 100644 index 00000000..6aa3b37d --- /dev/null +++ b/src/sample/complex/ComplexBuffer.java @@ -0,0 +1,42 @@ +package sample.complex; + +import java.util.Arrays; + +public class ComplexBuffer +{ + private float[] mSamples; + + /** + * Wrapper around float array containing interleaved I/Q samples + */ + public ComplexBuffer( float[] samples ) + { + mSamples = samples; + } + + /** + * Creates a deep copy of the buffer + */ + public ComplexBuffer copyOf() + { + float[] copy = Arrays.copyOf( mSamples, mSamples.length ); + + return new ComplexBuffer( copy ); + } + + /** + * Interleaved I/Q float sample array + */ + public float[] getSamples() + { + return mSamples; + } + + /** + * Cleanup method to nullify all data and references + */ + public void dispose() + { + mSamples = null; + } +} diff --git a/src/sample/complex/ComplexSampleBroadcaster.java b/src/sample/complex/ComplexSampleBroadcaster.java new file mode 100644 index 00000000..a70186c2 --- /dev/null +++ b/src/sample/complex/ComplexSampleBroadcaster.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * 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 + ******************************************************************************/ +package sample.complex; + +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Broadcasts received float samples to multiple listeners + */ +public class ComplexSampleBroadcaster implements ComplexSampleListener +{ + private CopyOnWriteArrayList mListeners = + new CopyOnWriteArrayList(); + + @Override + public void receive( float i, float q ) + { + broadcast( i, q ); + } + + /** + * Clear listeners to prepare for garbage collection + */ + public void dispose() + { + mListeners.clear(); + } + + public boolean hasListeners() + { + return !mListeners.isEmpty(); + } + + public int getListenerCount() + { + return mListeners.size(); + } + + public void addListener( ComplexSampleListener listener ) + { + mListeners.add( listener ); + } + + public void removeListener( ComplexSampleListener listener ) + { + mListeners.remove( listener ); + } + + public void clear() + { + mListeners.clear(); + } + + public void broadcast( float i, float q ) + { + for( ComplexSampleListener listener: mListeners ) + { + listener.receive( i, q ); + } + } +} diff --git a/src/sample/complex/ComplexSampleListener.java b/src/sample/complex/ComplexSampleListener.java new file mode 100644 index 00000000..1dca3f82 --- /dev/null +++ b/src/sample/complex/ComplexSampleListener.java @@ -0,0 +1,6 @@ +package sample.complex; + +public interface ComplexSampleListener +{ + public void receive( float i, float q ); +} diff --git a/src/sample/complex/RTLComplexSample.java b/src/sample/complex/RTLComplexSample.java deleted file mode 100644 index 5959e685..00000000 --- a/src/sample/complex/RTLComplexSample.java +++ /dev/null @@ -1,57 +0,0 @@ -/******************************************************************************* - * 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 - ******************************************************************************/ -package sample.complex; - -@Deprecated -public class RTLComplexSample extends ComplexSample -{ - private static final long serialVersionUID = 1L; - - private static float[] mValues; - - /** - * Creates a lookup table that converts the 8-bit valued range from 0 - 255 - * into scaled float values of -128 to 0 to 127 - */ - static - { - mValues = new float[ 256 ]; - - float scale = (float)( 1.0f / 127.0f ); - - for( int x = 0; x < 256; x++ ) - { - mValues[ x ] = (float)( x - 128 ) * scale; - } - - mValues[ 128 ] = 0.0000001f; - } - - /** - * Constructs a complex sample from the sample format produced by the - * RTL-2832 chip, where the byte values run from 0 - 255, but represent - * the value -127 to 127 - * - * @param left - * @param right - */ - public RTLComplexSample( byte left, byte right ) - { - super( mValues[ left & 0xFF ], mValues[ right & 0xFF ] ); - } -} diff --git a/src/sample/decimator/ComplexDecimator.java b/src/sample/decimator/ComplexDecimator.java new file mode 100644 index 00000000..a5bdb55d --- /dev/null +++ b/src/sample/decimator/ComplexDecimator.java @@ -0,0 +1,58 @@ +package sample.decimator; + +import sample.complex.ComplexSampleListener; + + +public class ComplexDecimator implements ComplexSampleListener +{ + private ComplexSampleListener mListener; + private int mCounter = 0; + private int mDecimationRate; + + /** + * Constructs a new Decimator object with the specified decimation rate. + */ + public ComplexDecimator( int rate ) + { + mDecimationRate = rate; + } + + public void dispose() + { + mListener = null; + } + + /** + * Sets a new decimation rate. Rate can be changed after this object + * is constructed. + */ + public synchronized void setDecimationRate( int rate ) + { + mDecimationRate = rate; + } + + /** + * Receives samples allowing only 1 of every (rate) sample to go on + * to the registered listener + */ + @Override + public void receive( float i, float q ) + { + mCounter++; + + if( mCounter >= mDecimationRate ) + { + mListener.receive( i, q ); + + mCounter = 0; + } + } + + /** + * Sets the decimated output listener + */ + public void setListener( ComplexSampleListener listener ) + { + mListener = listener; + } +} diff --git a/src/sample/Decimator.java b/src/sample/decimator/Decimator.java similarity index 97% rename from src/sample/Decimator.java rename to src/sample/decimator/Decimator.java index e6958a60..fe8c6661 100644 --- a/src/sample/Decimator.java +++ b/src/sample/decimator/Decimator.java @@ -15,7 +15,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see ******************************************************************************/ -package sample; +package sample.decimator; + +import sample.Listener; public class Decimator implements Listener diff --git a/src/sample/decimator/SimplexDecimator.java b/src/sample/decimator/SimplexDecimator.java new file mode 100644 index 00000000..315942dd --- /dev/null +++ b/src/sample/decimator/SimplexDecimator.java @@ -0,0 +1,58 @@ +package sample.decimator; + +import sample.complex.ComplexSampleListener; + + +public class SimplexDecimator implements ComplexSampleListener +{ + private ComplexSampleListener mListener; + private int mCounter = 0; + private int mDecimationRate; + + /** + * Constructs a new Decimator object with the specified decimation rate. + */ + public SimplexDecimator( int rate ) + { + mDecimationRate = rate; + } + + public void dispose() + { + mListener = null; + } + + /** + * Sets a new decimation rate. Rate can be changed after this object + * is constructed. + */ + public synchronized void setDecimationRate( int rate ) + { + mDecimationRate = rate; + } + + /** + * Receives samples allowing only 1 of every (rate) sample to go on + * to the registered listener + */ + @Override + public void receive( float i, float q ) + { + mCounter++; + + if( mCounter >= mDecimationRate ) + { + mListener.receive( i, q ); + + mCounter = 0; + } + } + + /** + * Sets the decimated output listener + */ + public void setListener( ComplexSampleListener listener ) + { + mListener = listener; + } +} diff --git a/src/sample/simplex/SimplexBuffer.java b/src/sample/simplex/SimplexBuffer.java new file mode 100644 index 00000000..f627b5d6 --- /dev/null +++ b/src/sample/simplex/SimplexBuffer.java @@ -0,0 +1,42 @@ +package sample.simplex; + +import java.util.Arrays; + +public class SimplexBuffer +{ + private float[] mSamples; + + /** + * Wrapper around float array containing float samples + */ + public SimplexBuffer( float[] samples ) + { + mSamples = samples; + } + + /** + * Creates a deep copy of the buffer + */ + public SimplexBuffer copyOf() + { + float[] copy = Arrays.copyOf( mSamples, mSamples.length ); + + return new SimplexBuffer( copy ); + } + + /** + * Sample data + */ + public float[] getSamples() + { + return mSamples; + } + + /** + * Cleanup method to nullify all data and references + */ + public void dispose() + { + mSamples = null; + } +} diff --git a/src/sample/simplex/SimplexSampleBroadcaster.java b/src/sample/simplex/SimplexSampleBroadcaster.java new file mode 100644 index 00000000..f5734894 --- /dev/null +++ b/src/sample/simplex/SimplexSampleBroadcaster.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * 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 + ******************************************************************************/ +package sample.simplex; + +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Broadcasts a received float sample to multiple listeners + */ +public class SimplexSampleBroadcaster implements SimplexSampleListener +{ + private CopyOnWriteArrayList mListeners = + new CopyOnWriteArrayList(); + + @Override + public void receive( float sample ) + { + broadcast( sample ); + } + + /** + * Clear listeners to prepare for garbage collection + */ + public void dispose() + { + mListeners.clear(); + } + + public boolean hasListeners() + { + return !mListeners.isEmpty(); + } + + public int getListenerCount() + { + return mListeners.size(); + } + + public void addListener( SimplexSampleListener listener ) + { + mListeners.add( listener ); + } + + public void removeListener( SimplexSampleListener listener ) + { + mListeners.remove( listener ); + } + + public void clear() + { + mListeners.clear(); + } + + public void broadcast( float sample ) + { + for( SimplexSampleListener listener: mListeners ) + { + listener.receive( sample ); + } + } +} diff --git a/src/sample/simplex/SimplexSampleListener.java b/src/sample/simplex/SimplexSampleListener.java new file mode 100644 index 00000000..bd63cf26 --- /dev/null +++ b/src/sample/simplex/SimplexSampleListener.java @@ -0,0 +1,6 @@ +package sample.simplex; + +public interface SimplexSampleListener +{ + public void receive( float sample ); +} diff --git a/src/sample/simplex/SimplexSampleProvider.java b/src/sample/simplex/SimplexSampleProvider.java new file mode 100644 index 00000000..73c29710 --- /dev/null +++ b/src/sample/simplex/SimplexSampleProvider.java @@ -0,0 +1,8 @@ +package sample.simplex; + +public interface SimplexSampleProvider +{ + public void setListener( SimplexSampleListener listener ); + + public void removeListener( SimplexSampleListener listener ); +} diff --git a/src/source/FloatArraySource.java b/src/source/FloatArraySource.java deleted file mode 100644 index da84e6fa..00000000 --- a/src/source/FloatArraySource.java +++ /dev/null @@ -1,12 +0,0 @@ -package source; - -import sample.Provider; -import source.Source.SampleType; - -public abstract class FloatArraySource extends Source implements Provider -{ - public FloatArraySource( String name ) - { - super( name, SampleType.FLOAT ); - } -} diff --git a/src/source/SimplexSource.java b/src/source/SimplexSource.java new file mode 100644 index 00000000..2c5ce78c --- /dev/null +++ b/src/source/SimplexSource.java @@ -0,0 +1,13 @@ +package source; + +import sample.Provider; +import sample.simplex.SimplexBuffer; + + +public abstract class SimplexSource extends Source implements Provider +{ + public SimplexSource( String name ) + { + super( name, SampleType.FLOAT ); + } +} diff --git a/src/source/mixer/ComplexMixer.java b/src/source/mixer/ComplexMixer.java new file mode 100644 index 00000000..5a92d8ae --- /dev/null +++ b/src/source/mixer/ComplexMixer.java @@ -0,0 +1,273 @@ +/******************************************************************************* + * 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 + ******************************************************************************/ +package source.mixer; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.TargetDataLine; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sample.Listener; +import sample.adapter.SampleAdapter; +import sample.complex.ComplexBuffer; +import sample.complex.ComplexSample; +import source.SourceException; + +public class ComplexMixer +{ + private final static Logger mLog = + LoggerFactory.getLogger( ComplexMixer.class ); + + private long mFrequency = 0; + private int mBufferSize = 16384; + + private BufferReader mBufferReader = new BufferReader(); + private TargetDataLine mTargetDataLine; + private AudioFormat mAudioFormat; + private String mName; + private int mBytesPerFrame = 0; + private SampleAdapter mSampleAdapter; + private Listener mListener; + + /** + * Complex Mixer - constructs a reader on the mixer/sound card target + * data line using the specified audio format (sample size, sample rate ) + * and broadcasts complex (I/Q) sample buffers to all registered listeners. + * Reads sample buffers sized to 10% of the sample rate specified in audio + * format. + * + * @param targetDataLine - mixer or sound card to be used + * + * @param format - audio format + * + * @param name - token name to use for this mixer + * + * @param sampleAdapter - adapter to convert byte array data read from the + * mixer into ComplexBuffer. The adapter can optionally invert the channel + * data if the left/right stereo channels are inverted. + */ + public ComplexMixer( TargetDataLine targetDataLine, + AudioFormat format, + String name, + SampleAdapter sampleAdapter, + Listener listener ) + { + mTargetDataLine = targetDataLine; + mName = name; + mAudioFormat = format; + mSampleAdapter = sampleAdapter; + mListener = listener; + } + + public TargetDataLine getTargetDataLine() + { + return mTargetDataLine; + } + + public void start() + { + if( !mBufferReader.isRunning() ) + { + Thread thread = new Thread( mBufferReader ); + thread.setDaemon( true ); + thread.setName( mName + " Complex Sample Reader" ); + thread.start(); + } + } + + public void stop() + { + mBufferReader.stop(); + } + + private List convert( ComplexBuffer sampleBuffer ) + { + float[] samples = sampleBuffer.getSamples(); + + ArrayList converted = new ArrayList(); + + for( int x = 0; x < samples.length; x += 2 ) + { + converted.add( new ComplexSample( samples[ x ], samples[ x + 1 ] ) ); + } + + sampleBuffer.dispose(); + + return converted; + } + + @Override + public String toString() + { + return mName; + } + + public int getSampleRate() + { + if( mTargetDataLine != null ) + { + return (int)mTargetDataLine.getFormat().getSampleRate(); + } + else + { + return 0; + } + } + + /** + * Returns the frequency of this source. Default is 0. + */ + public long getFrequency() throws SourceException + { + return mFrequency; + } + + /** + * Specify the frequency that will be returned from this source. This may + * be useful if you are streaming an external audio source in through the + * sound card and you want to specify a frequency for that source + */ + public void setFrequency( long frequency ) + { + mFrequency = frequency; + } + + /** + * Reader thread. Performs blocking read against the mixer target data + * line, converts the samples to an array of floats using the specified + * adapter. Dispatches float arrays to all registered listeners. + */ + public class BufferReader implements Runnable + { + private AtomicBoolean mRunning = new AtomicBoolean(); + + @Override + public void run() + { + if( mRunning.compareAndSet( false, true ) ) + { + if( mTargetDataLine == null ) + { + mRunning.set( false ); + + mLog.error( "ComplexMixerSource - mixer target data line" + + " is null" ); + } + else + { + mBytesPerFrame = mAudioFormat.getSampleSizeInBits() / 8; + + if( mBytesPerFrame == AudioSystem.NOT_SPECIFIED ) + { + mBytesPerFrame = 2; + } + + /* Set buffer size to 1/10 second of samples */ + mBufferSize = (int)( mAudioFormat.getSampleRate() + * 0.05 ) * mBytesPerFrame; + + /* We'll reuse the same buffer for each read */ + byte[] buffer = new byte[ mBufferSize ]; + + try + { + mTargetDataLine.open( mAudioFormat ); + + mTargetDataLine.start(); + } + catch ( LineUnavailableException e ) + { + mLog.error( "ComplexMixerSource - mixer target data line" + + "not available to read data from", e ); + + mRunning.set( false ); + } + + while( mRunning.get() ) + { + try + { + /* Blocking read - waits until the buffer fills */ + mTargetDataLine.read( buffer, 0, buffer.length ); + + /* Convert samples to float array */ + float[] samples = + mSampleAdapter.convert( buffer ); + + /* Dispatch samples to registered listeners */ + if( mListener != null ) + { + mListener.receive( new ComplexBuffer( samples ) ); + } + } + catch ( Exception e ) + { + mLog.error( "error while reading" + + "from the mixer target data line", e ); + + mRunning.set( false ); + } + } + } + + /* Close the data line if it is still open */ + if( mTargetDataLine != null && mTargetDataLine.isOpen() ) + { + mTargetDataLine.close(); + } + } + } + + /** + * Stops the reader thread + */ + public void stop() + { + if( mRunning.compareAndSet( true, false ) ) + { + mTargetDataLine.stop(); + mTargetDataLine.close(); + } + } + + /** + * Indicates if the reader thread is running + */ + public boolean isRunning() + { + return mRunning.get(); + } + } + + public void dispose() + { + if( mBufferReader != null ) + { + mBufferReader.stop(); + } + + mListener = null; + } +} diff --git a/src/source/mixer/MixerEditor.java b/src/source/mixer/MixerEditor.java index fb10f77b..e71884e8 100644 --- a/src/source/mixer/MixerEditor.java +++ b/src/source/mixer/MixerEditor.java @@ -17,7 +17,7 @@ ******************************************************************************/ package source.mixer; -import java.util.Set; +import java.util.List; import javax.sound.sampled.Mixer; import javax.swing.DefaultComboBoxModel; @@ -27,12 +27,13 @@ import javax.swing.JLabel; import source.SourceEditor; import source.config.SourceConfigMixer; import source.config.SourceConfiguration; +import source.mixer.MixerManager.DiscoveredMixer; import controller.ResourceManager; public class MixerEditor extends SourceEditor { private static final long serialVersionUID = 1L; - private JComboBox mComboMixers; + private JComboBox mComboMixers; private JComboBoxmComboChannels; protected Mixer.Info mSelectedMixer = null; @@ -50,7 +51,7 @@ public class MixerEditor extends SourceEditor JLabel mixerLabel = new JLabel( "Mixer:" ); add( mixerLabel, "align right" ); - mComboMixers = new JComboBox(); + mComboMixers = new JComboBox(); add( mComboMixers, "wrap" ); @@ -76,11 +77,12 @@ public class MixerEditor extends SourceEditor { SourceConfigMixer config = (SourceConfigMixer)mConfig; - String mixer = mComboMixers.getItemAt( mComboMixers.getSelectedIndex() ); + DiscoveredMixer selectedMixer = + mComboMixers.getItemAt( mComboMixers.getSelectedIndex() ); - if( mixer != null ) + if( selectedMixer != null ) { - config.setMixer( mixer ); + config.setMixer( selectedMixer.getMixerName() ); } MixerChannel channel = mComboChannels.getItemAt( @@ -103,16 +105,16 @@ public class MixerEditor extends SourceEditor @Override public void run() { - Set mixers = MixerManager.getInstance().getMixers(); - String[] mixerStrings = mixers.toArray( new String[ mixers.size() ] ); + DiscoveredMixer[] mixers = MixerManager.getInstance().getMixers(); mComboMixers.setModel( - new DefaultComboBoxModel( mixerStrings ) ); + new DefaultComboBoxModel( mixers ) ); SourceConfigMixer config = (SourceConfigMixer)mConfig; + String mixer = config.getMixer(); - if( mixer != null && mixers.contains( mixer ) ) + if( mixer != null ) { mComboMixers.setSelectedItem( mixer ); } @@ -121,11 +123,14 @@ public class MixerEditor extends SourceEditor //Mixer either hasn't been set yet, or it was set to a //mixer that is no longer available. Set the mixer to the //first item, and then store the value - if( mixers.size() > 0 ) + if( mixers.length > 0 ) { mComboMixers.setSelectedIndex( 0 ); - config.setMixer( mComboMixers.getItemAt( 0 ) ); + DiscoveredMixer selectedMixer = + (DiscoveredMixer)mComboMixers.getSelectedItem(); + + config.setMixer( selectedMixer.getMixerName() ); } } } diff --git a/src/source/mixer/MixerManager.java b/src/source/mixer/MixerManager.java index 4948bf03..cc44fe61 100644 --- a/src/source/mixer/MixerManager.java +++ b/src/source/mixer/MixerManager.java @@ -17,20 +17,25 @@ ******************************************************************************/ package source.mixer; +import java.util.ArrayList; import java.util.Collection; +import java.util.EnumSet; import java.util.HashMap; -import java.util.Set; +import java.util.List; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.DataLine; import javax.sound.sampled.Line; +import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.Mixer; import javax.sound.sampled.TargetDataLine; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sample.adapter.ChannelShortAdapter; +import sample.adapter.ShortAdapter; import source.config.SourceConfigMixer; import source.config.SourceConfiguration; import source.tuner.MixerTunerDataLine; @@ -44,8 +49,8 @@ public class MixerManager private static MixerManager sInstance = null; - private HashMap mMixerLines = - new HashMap(); + private ArrayList mDiscoveredMixers = + new ArrayList(); private HashMap mMixerTuners = new HashMap(); @@ -77,52 +82,109 @@ public class MixerManager return sInstance; } - public MixerSource getSource( ProcessingChain channel ) + public SimplexMixerSource getSource( ProcessingChain processingChain ) { - MixerSource retVal = null; + SimplexMixerSource retVal = null; SourceConfiguration config = - channel.getChannel().getSourceConfiguration(); + processingChain.getChannel().getSourceConfiguration(); if( config instanceof SourceConfigMixer ) { SourceConfigMixer mixerConfig = (SourceConfigMixer)config; - //Get the name of the requested mixer String mixerName = mixerConfig.getMixer(); if( mixerName != null ) { - TargetDataLine tdl = mMixerLines.get( mixerName ); + DiscoveredMixer mixer = getDiscoveredMixer( mixerName ); - if( tdl != null ) + if( mixer != null ) { - switch( mixerConfig.getChannel() ) + MixerChannel channel = mixerConfig.getChannel(); + + if( mixer.supportsChannel( channel ) ) { - case MONO: - retVal = new MixerSource( mMixerLines.get( mixerName ), - MONO, - mixerName, - new ShortAdapter() ); - break; - case LEFT: - case RIGHT: - retVal = new MixerSource( mMixerLines.get( mixerName ), - STEREO, - mixerName, - new ChannelShortAdapter( mixerConfig.getChannel() ) ); - break; + + if( channel == MixerChannel.MONO ) + { + DataLine.Info info = + new DataLine.Info(TargetDataLine.class, MONO ); + + TargetDataLine dataLine; + + try + { + dataLine = (TargetDataLine)mixer + .getMixer().getLine( info ); + + if( dataLine != null ) + { + return new SimplexMixerSource( dataLine, + MONO, + mixerName, + new ShortAdapter() ); + } + } + catch ( LineUnavailableException e ) + { + mLog.error( "couldn't get mixer data line " + + "for [" + mixerName + "] for channel [" + + channel.name() + "]", e ); + } + + } + else + { + DataLine.Info info = + new DataLine.Info(TargetDataLine.class, STEREO ); + + TargetDataLine dataLine; + + try + { + dataLine = (TargetDataLine)mixer + .getMixer().getLine( info ); + + if( dataLine != null ) + { + return new SimplexMixerSource( dataLine, STEREO, + mixerName, new ChannelShortAdapter( + mixerConfig.getChannel() ) ); + } + } + catch ( LineUnavailableException e ) + { + mLog.error( "couldn't get mixer data line " + + "for [" + mixerName + "] for channel [" + + channel.name() + "]", e ); + } + } } } } } - return retVal; + return null; } - public Set getMixers() + public DiscoveredMixer[] getMixers() { - return mMixerLines.keySet(); + return mDiscoveredMixers.toArray( + new DiscoveredMixer[ mDiscoveredMixers.size() ] ); + } + + public DiscoveredMixer getDiscoveredMixer( String name ) + { + for( DiscoveredMixer mixer: mDiscoveredMixers ) + { + if( mixer.getMixerName().contentEquals( name ) ) + { + return mixer; + } + } + + return null; } public Collection getMixerTunerDataLines() @@ -147,22 +209,27 @@ public class MixerManager if( mixer != null ) { - TargetDataLine tdl = getTargetDataLine( mixerInfo, STEREO ); - - if( tdl != null ) + EnumSet channels = getSupportedChannels( mixer ); + + if( channels != null ) { - mMixerLines.put( mixerInfo.getName(), tdl ); - - sb.append( "\t[LOADED] Mixer:" + mixerInfo.getName() + "\n" ); + mDiscoveredMixers.add( + new DiscoveredMixer( mixer, channels ) ); + + sb.append( "\t[LOADED] Mixer:" + mixerInfo.getName() + + " CHANNELS: " + channels + "\n" ); } else { - sb.append( "\t[NOT LOADED] Mixer:" + mixerInfo.getName() + " - audio format not supported\n" ); + sb.append( "\t[NOT LOADED] Mixer:" + + mixerInfo.getName() + + " - audio format not supported\n" ); } } else { - sb.append( "\t[NOT LOADED] Mixer:" + mixerInfo.getName() + " - couldn't get mixer\n" ); + sb.append( "\t[NOT LOADED] Mixer:" + mixerInfo.getName() + + " - couldn't get mixer\n" ); } } else //Process as a Mixer-based tuner data line @@ -226,6 +293,38 @@ public class MixerManager return retVal; } + private EnumSet getSupportedChannels( Mixer mixer ) + { + DataLine.Info stereoInfo= + new DataLine.Info(TargetDataLine.class, STEREO ); + + boolean stereoSupported = mixer.isLineSupported( stereoInfo ); + + DataLine.Info monoInfo= + new DataLine.Info(TargetDataLine.class, MONO ); + + boolean monoSupported = mixer.isLineSupported( monoInfo ); + + if( stereoSupported && monoSupported ) + { + return EnumSet.of( MixerChannel.LEFT, + MixerChannel.RIGHT, + MixerChannel.MONO ); + } + else if( stereoSupported ) + { + return EnumSet.of( MixerChannel.LEFT, + MixerChannel.RIGHT, + MixerChannel.MONO ); + } + else if( monoSupported ) + { + return EnumSet.of( MixerChannel.MONO ); + } + + return null; + } + public static String getMixerDevices() { StringBuilder sb = new StringBuilder(); @@ -264,4 +363,41 @@ public class MixerManager return sb.toString(); } + + public class DiscoveredMixer + { + private Mixer mMixer; + private EnumSet mChannels; + + public DiscoveredMixer( Mixer mixer, EnumSet channels ) + { + mMixer = mixer; + mChannels = channels; + } + + public Mixer getMixer() + { + return mMixer; + } + + public String getMixerName() + { + return mMixer.getMixerInfo().getName(); + } + + public EnumSet getChannels() + { + return mChannels; + } + + public boolean supportsChannel( MixerChannel channel ) + { + return mChannels.contains( channel ); + } + + public String toString() + { + return getMixerName(); + } + } } diff --git a/src/source/mixer/MixerSource.java b/src/source/mixer/SimplexMixerSource.java similarity index 86% rename from src/source/mixer/MixerSource.java rename to src/source/mixer/SimplexMixerSource.java index 163e315b..ae2ca924 100644 --- a/src/source/mixer/MixerSource.java +++ b/src/source/mixer/SimplexMixerSource.java @@ -17,7 +17,6 @@ ******************************************************************************/ package source.mixer; -import java.util.Arrays; import java.util.Iterator; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; @@ -31,13 +30,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sample.Listener; -import source.FloatArraySource; +import sample.adapter.SampleAdapter; +import sample.simplex.SimplexBuffer; +import source.SimplexSource; import source.SourceException; -public class MixerSource extends FloatArraySource +public class SimplexMixerSource extends SimplexSource { private final static Logger mLog = - LoggerFactory.getLogger( MixerSource.class ); + LoggerFactory.getLogger( SimplexMixerSource.class ); private long mFrequency = 0; private int mBufferSize = 16384; @@ -49,14 +50,14 @@ public class MixerSource extends FloatArraySource private int mBytesPerFrame = 0; private SampleAdapter mSampleAdapter; - CopyOnWriteArrayList> mSampleListeners = - new CopyOnWriteArrayList>(); + CopyOnWriteArrayList> mSampleListeners = + new CopyOnWriteArrayList>(); /** - * Complex Mixer Source - constructs a reader on the mixer/sound card target + * Simplex Mixer Source - constructs a reader on the mixer/sound card target * data line using the specified audio format (sample size, sample rate ) - * and broadcasts float[] sample data to all registered listeners. Reads - * sample buffers sized to 10% of the sample rate specified in audio format. + * and broadcasts simplex buffers (float arrays) to all registered listeners. + * Reads buffers sized to 10% of the sample rate specified in audio format. * * @param targetDataLine - mixer or sound card to be used * @@ -68,7 +69,7 @@ public class MixerSource extends FloatArraySource * mixer into float array data. Can optionally invert the channel data if * the left/right stereo channels are inverted. */ - public MixerSource( TargetDataLine targetDataLine, + public SimplexMixerSource( TargetDataLine targetDataLine, AudioFormat format, String name, SampleAdapter sampleAdapter ) @@ -80,7 +81,7 @@ public class MixerSource extends FloatArraySource mSampleAdapter = sampleAdapter; } - public void setListener( Listener listener ) + public void setListener( Listener listener ) { mSampleListeners.add( listener ); @@ -88,12 +89,13 @@ public class MixerSource extends FloatArraySource if( !mBufferReader.isRunning() ) { Thread thread = new Thread( mBufferReader ); + thread.setDaemon( true ); thread.setName( getName() + " Sample Reader" ); thread.start(); } } - public void removeListener( Listener listener ) + public void removeListener( Listener listener ) { mSampleListeners.remove( listener ); @@ -104,19 +106,19 @@ public class MixerSource extends FloatArraySource } } - public void broadcast( Float[] samples ) + public void broadcast( SimplexBuffer samples ) { - Iterator> it = mSampleListeners.iterator(); + Iterator> it = mSampleListeners.iterator(); while( it.hasNext() ) { - Listener next = it.next(); + Listener next = it.next(); /* if this is the last (or only) listener, send him the original * buffer, otherwise send him a copy of the buffer */ if( it.hasNext() ) { - next.receive( Arrays.copyOf( samples, samples.length ) ); + next.receive( samples.copyOf() ); } else { @@ -220,11 +222,11 @@ public class MixerSource extends FloatArraySource mTargetDataLine.read( buffer, 0, buffer.length ); /* Convert samples to float array */ - Float[] convertedSamples = + float[] samples = mSampleAdapter.convert( buffer ); /* Dispatch samples to registered listeners */ - broadcast( convertedSamples ); + broadcast( new SimplexBuffer( samples ) ); } catch ( Exception e ) { diff --git a/src/source/tuner/MixerTuner.java b/src/source/tuner/MixerTuner.java index 57d698fc..0de4ef66 100644 --- a/src/source/tuner/MixerTuner.java +++ b/src/source/tuner/MixerTuner.java @@ -25,25 +25,25 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sample.Listener; +import sample.adapter.SampleAdapter; +import sample.complex.ComplexBuffer; import sample.complex.ComplexSampleListListener; -import source.mixer.MixerSource; -import source.mixer.SampleAdapter; +import source.mixer.ComplexMixer; -public abstract class MixerTuner extends Tuner +public abstract class MixerTuner extends Tuner implements Listener { private final static Logger mLog = LoggerFactory.getLogger( MixerTuner.class ); - private TargetDataLine mTargetDataLine; protected MixerTunerType mMixerTunerType; - protected MixerSource mMixerSource; + protected ComplexMixer mComplexMixer; protected ArrayList mListeners = new ArrayList(); /** - * Provides a wrapper around a MixerSource to treat it like a Tuner that - * can provide ComplexSample samples. Subclasses should couple this - * functionality with a tuner control class to allow tuning. + * Wrapper class to add basic Tuner functionality to the ComplexMixer. + * Subclasses should couple this functionality with a tuner controller class + * to support tuning. */ public MixerTuner( String name, MixerTunerType mixerTunerType, @@ -54,12 +54,11 @@ public abstract class MixerTuner extends Tuner mMixerTunerType = mixerTunerType; - mTargetDataLine = targetDataLine; - - mMixerSource = new MixerSource( targetDataLine, - mMixerTunerType.getAudioFormat(), - name, - sampleAdapter ); + mComplexMixer = new ComplexMixer( targetDataLine, + mMixerTunerType.getAudioFormat(), + name, + sampleAdapter, + (Listener)this ); } public MixerTuner( String name, @@ -72,6 +71,13 @@ public abstract class MixerTuner extends Tuner sampleAdapter ); } + public void dispose() + { + mComplexMixer.stop(); + + super.dispose(); + } + public MixerTunerType getMixerTunerType() { return mMixerTunerType; @@ -79,26 +85,44 @@ public abstract class MixerTuner extends Tuner public TargetDataLine getTargetDataLine() { - return mTargetDataLine; + return mComplexMixer.getTargetDataLine(); } /** - * Overrides the Tuner listener management methods and allows the - * complex mixer source to manage sample listeners + * Dispatches the received complex buffers from the mComplexMixer to all + * listeners registered on the parent Tuner class */ @Override - public void addListener( Listener listener ) + public void receive( ComplexBuffer buffer ) + { + super.broadcast( buffer ); + } + + /** + * Overrides the parent Tuner class listener management method to allow + * starting the buffer processing thread + */ + @Override + public void addListener( Listener listener ) { - mMixerSource.setListener( listener ); + super.addListener( listener ); + + /* We can call start multiple times -- it ignores additional requests */ + mComplexMixer.start(); } /** - * Overrides the Tuner listener management methods and allows the - * complex mixer source to manage sample listeners + * Overrides the parent Tuner class listener management method to allow + * stopping the buffer processing thread when there are no more listeners */ @Override - public void removeListener( Listener listener ) + public void removeListener( Listener listener ) { - mMixerSource.removeListener( listener ); + super.removeListener( listener ); + + if( mSampleListeners.isEmpty() ) + { + mComplexMixer.stop(); + } } } diff --git a/src/source/tuner/Tuner.java b/src/source/tuner/Tuner.java index b8eb056e..2076d3ca 100644 --- a/src/source/tuner/Tuner.java +++ b/src/source/tuner/Tuner.java @@ -23,6 +23,7 @@ import java.util.concurrent.RejectedExecutionException; import javax.swing.JPanel; import sample.Listener; +import sample.complex.ComplexBuffer; import source.SourceException; import source.tuner.FrequencyChangeEvent.Attribute; import controller.ResourceManager; @@ -37,8 +38,12 @@ public abstract class Tuner implements FrequencyChangeBroadcaster, { private String mName; - protected CopyOnWriteArrayList> - mSampleListeners = new CopyOnWriteArrayList>(); + /** + * Sample Listeners - these will typically be the DFT processor for spectral + * display, or it will be one or more tuner channel sources + */ + protected CopyOnWriteArrayList> + mSampleListeners = new CopyOnWriteArrayList>(); protected CopyOnWriteArrayList mFrequencyChangeListeners = new CopyOnWriteArrayList(); @@ -53,7 +58,11 @@ public abstract class Tuner implements FrequencyChangeBroadcaster, return mName; } - public abstract void dispose(); + public void dispose() + { + mSampleListeners.clear(); + mFrequencyChangeListeners.clear(); + } public void setName( String name ) { @@ -141,7 +150,7 @@ public abstract class Tuner implements FrequencyChangeBroadcaster, /** * Registers the listener to receive complex float sample arrays */ - public void addListener( Listener listener ) + public void addListener( Listener listener ) { mSampleListeners.add( listener ); } @@ -149,7 +158,7 @@ public abstract class Tuner implements FrequencyChangeBroadcaster, /** * Removes the registered listener */ - public void removeListener( Listener listener ) + public void removeListener( Listener listener ) { mSampleListeners.remove( listener ); } @@ -157,11 +166,11 @@ public abstract class Tuner implements FrequencyChangeBroadcaster, /** * Broadcasts the samples to all registered listeners */ - public void broadcast( Float[] samples ) + public void broadcast( ComplexBuffer sampleBuffer ) { - for( Listener listener: mSampleListeners ) + for( Listener listener: mSampleListeners ) { - listener.receive( samples ); + listener.receive( sampleBuffer ); } } @@ -228,15 +237,4 @@ public abstract class Tuner implements FrequencyChangeBroadcaster, { broadcastFrequencyChangeEvent( event ); } - -// /** -// * Broadcasts a frequency change event to all registered listeners -// */ -// public void broadcastBandwidthChange( int bandwidth ) -// { -// broadcastFrequencyChangeEvent( -// new FrequencyChangeEvent( Attribute.BANDWIDTH, bandwidth ) ); -// } -// - } diff --git a/src/source/tuner/TunerChannelSource.java b/src/source/tuner/TunerChannelSource.java index ac07d76c..bf6a96da 100644 --- a/src/source/tuner/TunerChannelSource.java +++ b/src/source/tuner/TunerChannelSource.java @@ -25,6 +25,7 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import sample.Listener; +import sample.complex.ComplexBuffer; import sample.complex.ComplexSample; import sample.complex.ComplexSampleAssembler; import source.ComplexSource; @@ -39,13 +40,13 @@ import dsp.filter.Window.WindowType; public class TunerChannelSource extends ComplexSource implements FrequencyChangeListener, - Listener + Listener { private static int sCHANNEL_RATE = 48000; private static int sCHANNEL_PASS_FREQUENCY = 12500; - private LinkedTransferQueue mBuffer = - new LinkedTransferQueue(); + private LinkedTransferQueue mBuffer = + new LinkedTransferQueue(); private Tuner mTuner; private TunerChannel mTunerChannel; private Oscillator mSineWaveGenerator; @@ -91,7 +92,7 @@ public class TunerChannelSource extends ComplexSource new DecimationProcessor(), 50, TimeUnit.MILLISECONDS ); /* Finally, register to receive samples from the tuner */ - mTuner.addListener( (Listener)this ); + mTuner.addListener( (Listener)this ); } @Override @@ -137,9 +138,9 @@ public class TunerChannelSource extends ComplexSource } @Override - public void receive( Float[] samples ) + public void receive( ComplexBuffer sampleArray ) { - mBuffer.add( samples ); + mBuffer.add( sampleArray ); } @Override @@ -242,17 +243,20 @@ public class TunerChannelSource extends ComplexSource @Override public void run() { - List samplesList = new ArrayList(); + List sampleBuffers = + new ArrayList(); if( mBuffer != null ) { /* Limit to 10, so that after a garbage collect we don't * induce wild swings as it tries to take tons of samples * and process them, causing delays and buffer refill */ - mBuffer.drainTo( samplesList, 10 ); + mBuffer.drainTo( sampleBuffers, 10 ); - for( Float[] samples: samplesList ) + for( ComplexBuffer sampleArray: sampleBuffers ) { + float[] samples = sampleArray.getSamples(); + for( int x = 0; x < samples.length; x += 2 ) { Float left = samples[ x ]; @@ -273,7 +277,7 @@ public class TunerChannelSource extends ComplexSource } } - samplesList.clear(); + sampleBuffers.clear(); } } } diff --git a/src/source/tuner/TunerController.java b/src/source/tuner/TunerController.java index 27621896..cdca2734 100644 --- a/src/source/tuner/TunerController.java +++ b/src/source/tuner/TunerController.java @@ -135,6 +135,10 @@ public abstract class TunerController implements Tunable long maxLockedFrequency = mTunedChannels .get( mTunedChannels.size() - 1 ).getMaxFrequency(); + mLog.debug( "Tuner Controller min [" + minLockedFrequency + + "] max [" + maxLockedFrequency + + "] bw [" + getBandwidth() + "]" ); + //Requested channel is higher than min locked frequency if( minLockedFrequency <= channel.getMinFrequency() && ( channel.getMaxFrequency() - minLockedFrequency ) <= getBandwidth() ) diff --git a/src/source/tuner/TunerManager.java b/src/source/tuner/TunerManager.java index 01e0f7e3..c069ccd3 100644 --- a/src/source/tuner/TunerManager.java +++ b/src/source/tuner/TunerManager.java @@ -364,7 +364,14 @@ public class TunerManager HackRFTuner tuner = new HackRFTuner( hackRFController ); - return new TunerInitStatus( tuner, "LOADED" ); + TunerConfiguration config = getTunerConfiguration( tuner ); + + if( config != null ) + { + tuner.apply( config ); + } + + return new TunerInitStatus( tuner, "LOADED" ); } catch( SourceException se ) { @@ -475,98 +482,6 @@ public class TunerManager return new TunerInitStatus( null, reason ); } - -// /** -// * Get all USB tuner devices -// */ -// private ArrayList getUSBTunerDevices() -// { -// StringBuilder sb = new StringBuilder(); -// -// List devices = getUSBDevices(); -// -// sb.append( "discovered [" + devices.size() + -// "] attached USB devices\n" ); -// -// ArrayList tuners = new ArrayList(); -// -// for (UsbDevice device : devices ) -// { -// String status = "\t[NOT RECOGNIZED]\t"; -// -// TunerClass type = TunerClass.valueOf( device.getUsbDeviceDescriptor() ); -// -// if( type != TunerClass.UNKNOWN ) -// { -// status = "\t[RECOGNIZED] \t"; -// tuners.add( new USBTunerDevice( device, type) ); -// } -// -// sb.append( status ); -// sb.append( device.toString() ); -// -// if( type != TunerClass.UNKNOWN ) -// { -// sb.append( " " ); -// sb.append( type.getVendorDescription() ); -// sb.append( " " ); -// sb.append( type.getDeviceDescription() ); -// } -// -// sb.append( "\n" ); -// } -// -// mLog.info( "USB Device Discovery" ); -// mLog.info( sb.toString() ); -// -// return tuners; -// } -// -// /** -// * Gets all USB devices from the root hub downward, recursively, or -// * returns empty list if none are discovered -// */ -// public static List getUSBDevices() -// { -// ArrayList devices = new ArrayList(); -// -// try -// { -// UsbServices services = UsbHostManager.getUsbServices(); -// devices = getUsbChildDevices( services.getRootUsbHub() ); -// } -// catch( SecurityException e ) -// { -// mLog.error( "security exception while getting USB devices", e ); -// } -// catch( UsbException e ) -// { -// mLog.error( "usb exception while getting USB devices", e ); -// } -// -// return devices; -// } -// -// /** -// * Get (recursively) all usb child devices below the usb hub -// */ -// public static ArrayList getUsbChildDevices( UsbHub hub ) -// { -// ArrayList devices = new ArrayList(); -// -// for ( UsbDevice device : (List) hub.getAttachedUsbDevices() ) -// { -// devices.add( device ); -// -// if ( device.isUsbHub() ) -// { -// devices.addAll( getUsbChildDevices( (UsbHub)device ) ); -// } -// } -// -// return devices; -// } - /** * Get a stored tuner configuration or create a default tuner configuration * for the tuner type, connected at the address. diff --git a/src/source/tuner/fcd/FCDTuner.java b/src/source/tuner/fcd/FCDTuner.java index 941ea4c7..ae697917 100644 --- a/src/source/tuner/fcd/FCDTuner.java +++ b/src/source/tuner/fcd/FCDTuner.java @@ -22,8 +22,9 @@ import java.util.concurrent.RejectedExecutionException; import javax.swing.JPanel; import sample.Listener; +import sample.adapter.ShortAdapter; +import sample.complex.ComplexBuffer; import source.SourceException; -import source.mixer.ShortAdapter; import source.tuner.FrequencyChangeListener; import source.tuner.MixerTuner; import source.tuner.MixerTunerDataLine; @@ -122,7 +123,7 @@ public class FCDTuner extends MixerTuner if( source != null ) { /* Unregister for receiving samples */ - removeListener( (Listener)source ); + removeListener( (Listener)source ); /* Tell the controller to release the channel and cleanup */ /* This will release the channel as a frequency change listener */ diff --git a/src/source/tuner/hackrf/HackRFTuner.java b/src/source/tuner/hackrf/HackRFTuner.java index 8ac701a4..ee13c649 100644 --- a/src/source/tuner/hackrf/HackRFTuner.java +++ b/src/source/tuner/hackrf/HackRFTuner.java @@ -26,6 +26,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sample.Listener; +import sample.complex.ComplexBuffer; import source.SourceException; import source.tuner.Tuner; import source.tuner.TunerChannel; @@ -102,7 +103,7 @@ public class HackRFTuner extends Tuner @Override public void apply( TunerConfiguration config ) throws SourceException { - mLog.error( "B100 Tuner - can't apply tuner configuration" ); + mController.apply( config ); } @Override @@ -128,7 +129,7 @@ public class HackRFTuner extends Tuner public void releaseChannel( TunerChannelSource source ) { /* Unregister for receiving samples */ - removeListener( (Listener)source ); + removeListener( (Listener)source ); /* Tell the controller to release the channel and cleanup */ if( source != null ) @@ -153,13 +154,13 @@ public class HackRFTuner extends Tuner } @Override - public void addListener( Listener listener ) + public void addListener( Listener listener ) { mController.addListener( listener ); } @Override - public void removeListener( Listener listener ) + public void removeListener( Listener listener ) { mController.removeListener( listener ); } diff --git a/src/source/tuner/hackrf/HackRFTunerController.java b/src/source/tuner/hackrf/HackRFTunerController.java index 741e4063..f38051a8 100644 --- a/src/source/tuner/hackrf/HackRFTunerController.java +++ b/src/source/tuner/hackrf/HackRFTunerController.java @@ -53,7 +53,6 @@ package source.tuner.hackrf; * along with this program. If not, see ******************************************************************************/ import java.nio.ByteBuffer; - import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; @@ -80,6 +79,7 @@ import org.usb4java.Transfer; import org.usb4java.TransferCallback; import sample.Listener; +import sample.complex.ComplexBuffer; import source.SourceException; import source.tuner.TunerConfiguration; import source.tuner.TunerController; @@ -114,8 +114,8 @@ public class HackRFTunerController extends TunerController private LinkedTransferQueue mFilledBuffers = new LinkedTransferQueue(); - private CopyOnWriteArrayList> mSampleListeners = - new CopyOnWriteArrayList>(); + private CopyOnWriteArrayList> mSampleListeners = + new CopyOnWriteArrayList>(); private ByteSampleAdapter mSampleAdapter = new ByteSampleAdapter(); private BufferProcessor mBufferProcessor = new BufferProcessor(); @@ -491,6 +491,8 @@ public class HackRFTunerController extends TunerController */ public void setSampleRate( HackRFSampleRate rate ) throws UsbException { + mLog.debug( "Setting sample rate to:" + rate.name() ); + setSampleRateManual( rate.getRate(), 1 ); mFrequencyController.setSampleRate( rate.getRate() ); @@ -561,11 +563,11 @@ public class HackRFTunerController extends TunerController RATE2_016MHZ( 2016000, "2.016 MHz", BasebandFilter.F3_50 ), RATE3_024MHZ( 3024000, "3.024 MHz", BasebandFilter.F5_00 ), RATE4_464MHZ( 4464000, "4.464 MHz", BasebandFilter.F6_00 ), - RATE5_472MHZ( 5472000, "5.472 MHz", BasebandFilter.F7_00 ), + RATE5_376MHZ( 5376000, "5.376 MHz", BasebandFilter.F7_00 ), RATE7_488MHZ( 7488000, "7.488 MHz", BasebandFilter.F9_00 ), - RATE10_032MHZ( 9984000, " 9.984 MHz", BasebandFilter.F12_00 ), + RATE10_080MHZ( 10080000, "10.080 MHz", BasebandFilter.F12_00 ), RATE12_000MHZ( 12000000, "12.000 MHz", BasebandFilter.F14_00 ), - RATE13_488MHZ( 13488000, "13.488 MHz", BasebandFilter.F15_00 ), + RATE13_440MHZ( 13440000, "13.440 MHz", BasebandFilter.F15_00 ), RATE14_976MHZ( 14976000, "14.976 MHz", BasebandFilter.F20_00 ), RATE19_968MHZ( 19968000, "19.968 MHz", BasebandFilter.F24_00 ); @@ -868,7 +870,7 @@ public class HackRFTunerController extends TunerController * Adds a sample listener. If the buffer processing thread is * not currently running, starts it running in a new thread. */ - public void addListener( Listener listener ) + public void addListener( Listener listener ) { mSampleListeners.add( listener ); @@ -878,7 +880,7 @@ public class HackRFTunerController extends TunerController Thread thread = new Thread( mBufferProcessor ); thread.setDaemon( true ); - thread.setName( "Sample Processor" ); + thread.setName( "HackRF Sample Processor" ); thread.start(); } @@ -888,7 +890,7 @@ public class HackRFTunerController extends TunerController * Removes the sample listener. If this is the last registered listener, * shuts down the buffer processing thread. */ - public void removeListener( Listener listener ) + public void removeListener( Listener listener ) { mSampleListeners.remove( listener ); @@ -906,19 +908,19 @@ public class HackRFTunerController extends TunerController * This is to facilitate garbage collection of the array when the listener * is done processing the samples. */ - public void broadcast( Float[] samples ) + public void broadcast( ComplexBuffer samples ) { - Iterator> it = mSampleListeners.iterator(); + Iterator> it = mSampleListeners.iterator(); while( it.hasNext() ) { - Listener next = it.next(); + Listener next = it.next(); /* if this is the last (or only) listener, send the original * buffer, otherwise send a copy of the buffer */ if( it.hasNext() ) { - next.receive( Arrays.copyOf( samples, samples.length ) ); + next.receive( samples.copyOf() ); } else { @@ -936,7 +938,7 @@ public class HackRFTunerController extends TunerController private ScheduledExecutorService mExecutor = Executors.newScheduledThreadPool( 1 ); private ScheduledFuture mSampleDispatcherTask; - private ArrayList mTransfers; + private CopyOnWriteArrayList mTransfers; private AtomicBoolean mRunning = new AtomicBoolean(); @Override @@ -1078,7 +1080,7 @@ public class HackRFTunerController extends TunerController */ private void prepareTransfers() throws LibUsbException { - mTransfers = new ArrayList(); + mTransfers = new CopyOnWriteArrayList(); for( int x = 0; x < TRANSFER_BUFFER_POOL_SIZE; x++ ) { @@ -1121,10 +1123,9 @@ public class HackRFTunerController extends TunerController for( byte[] buffer: buffers ) { - //TODO: convert this to float primitive - Float[] samples = mSampleAdapter.convert( buffer ); + float[] samples = mSampleAdapter.convert( buffer ); - broadcast( samples ); + broadcast( new ComplexBuffer( samples ) ); } } } diff --git a/src/source/tuner/rtl/ByteSampleAdapter.java b/src/source/tuner/rtl/ByteSampleAdapter.java index cecfe182..fb140835 100644 --- a/src/source/tuner/rtl/ByteSampleAdapter.java +++ b/src/source/tuner/rtl/ByteSampleAdapter.java @@ -17,7 +17,8 @@ ******************************************************************************/ package source.tuner.rtl; -import source.mixer.SampleAdapter; +import sample.adapter.SampleAdapter; +import sample.complex.ComplexBuffer; public class ByteSampleAdapter extends SampleAdapter { @@ -40,9 +41,9 @@ public class ByteSampleAdapter extends SampleAdapter } @Override - public Float[] convert( byte[] samples ) + public float[] convert( byte[] samples ) { - Float[] convertedSamples = new Float[ samples.length ]; + float[] convertedSamples = new float[ samples.length ]; int pointer = 0; for( byte sample: samples ) diff --git a/src/source/tuner/rtl/RTL2832Tuner.java b/src/source/tuner/rtl/RTL2832Tuner.java index 0e70a26b..7f421b7b 100644 --- a/src/source/tuner/rtl/RTL2832Tuner.java +++ b/src/source/tuner/rtl/RTL2832Tuner.java @@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory; import org.usb4java.LibUsbException; import sample.Listener; +import sample.complex.ComplexBuffer; import source.SourceException; import source.tuner.FrequencyChangeListener; import source.tuner.Tuner; @@ -165,7 +166,7 @@ public class RTL2832Tuner extends Tuner public void releaseChannel( TunerChannelSource source ) { /* Unregister for receiving samples */ - removeListener( (Listener)source ); + removeListener( (Listener)source ); /* Tell the controller to release the channel and cleanup */ if( source != null ) @@ -175,13 +176,13 @@ public class RTL2832Tuner extends Tuner } @Override - public void addListener( Listener listener ) + public void addListener( Listener listener ) { mController.addListener( listener ); } @Override - public void removeListener( Listener listener ) + public void removeListener( Listener listener ) { mController.removeListener( listener ); } diff --git a/src/source/tuner/rtl/RTL2832TunerController.java b/src/source/tuner/rtl/RTL2832TunerController.java index 51eee6cd..ba067b5d 100644 --- a/src/source/tuner/rtl/RTL2832TunerController.java +++ b/src/source/tuner/rtl/RTL2832TunerController.java @@ -52,12 +52,12 @@ import org.usb4java.Transfer; import org.usb4java.TransferCallback; import sample.Listener; +import sample.complex.ComplexBuffer; import source.SourceException; import source.tuner.FrequencyChangeEvent; import source.tuner.FrequencyChangeEvent.Attribute; import source.tuner.TunerController; import source.tuner.TunerType; -import util.TimeStamp; import buffer.FloatAveragingBuffer; import controller.ResourceManager; @@ -107,8 +107,8 @@ public abstract class RTL2832TunerController extends TunerController private ByteSampleAdapter mSampleAdapter = new ByteSampleAdapter(); - private CopyOnWriteArrayList> mSampleListeners = - new CopyOnWriteArrayList>(); + private CopyOnWriteArrayList> mSampleListeners = + new CopyOnWriteArrayList>(); private LinkedTransferQueue mFilledBuffers = new LinkedTransferQueue(); @@ -1342,7 +1342,7 @@ public abstract class RTL2832TunerController extends TunerController * Adds a sample listener. If the buffer processing thread is * not currently running, starts it running in a new thread. */ - public void addListener( Listener listener ) + public void addListener( Listener listener ) { mSampleListeners.add( listener ); @@ -1352,7 +1352,7 @@ public abstract class RTL2832TunerController extends TunerController Thread thread = new Thread( mBufferProcessor ); thread.setDaemon( true ); - thread.setName( "Sample Processor" ); + thread.setName( "RTL2832 Sample Processor" ); thread.start(); } @@ -1362,7 +1362,7 @@ public abstract class RTL2832TunerController extends TunerController * Removes the sample listener. If this is the last registered listener, * shuts down the buffer processing thread. */ - public void removeListener( Listener listener ) + public void removeListener( Listener listener ) { mSampleListeners.remove( listener ); @@ -1375,23 +1375,23 @@ public abstract class RTL2832TunerController extends TunerController /** * Dispatches float sample buffers to all registered listeners */ - public void broadcast( Float[] samples ) + public void broadcast( ComplexBuffer buffer ) { - Iterator> it = mSampleListeners.iterator(); + Iterator> it = mSampleListeners.iterator(); while( it.hasNext() ) { - Listener next = it.next(); + Listener next = it.next(); /* if this is the last (or only) listener, send him the original * buffer, otherwise send him a copy of the buffer */ if( it.hasNext() ) { - next.receive( Arrays.copyOf( samples, samples.length ) ); + next.receive( buffer.copyOf() ); } else { - next.receive( samples ); + next.receive( buffer ); } } } @@ -1406,7 +1406,7 @@ public abstract class RTL2832TunerController extends TunerController Executors.newScheduledThreadPool( 2 ); private ScheduledFuture mSampleDispatcherTask; private ScheduledFuture mSampleRateCounterTask; - private ArrayList mTransfers; + private CopyOnWriteArrayList mTransfers; private AtomicBoolean mRunning = new AtomicBoolean(); @Override @@ -1573,7 +1573,7 @@ public abstract class RTL2832TunerController extends TunerController */ private void prepareTransfers() throws LibUsbException { - mTransfers = new ArrayList(); + mTransfers = new CopyOnWriteArrayList(); for( int x = 0; x < TRANSFER_BUFFER_POOL_SIZE; x++ ) { @@ -1616,10 +1616,9 @@ public abstract class RTL2832TunerController extends TunerController for( byte[] buffer: buffers ) { - //TODO: convert this to float primitive - Float[] samples = mSampleAdapter.convert( buffer ); + float[] samples = mSampleAdapter.convert( buffer ); - broadcast( samples ); + broadcast( new ComplexBuffer( samples ) ); } } } diff --git a/src/source/wave/FloatWaveSource.java b/src/source/wave/FloatWaveSource.java index bb9a7439..471f8e2d 100644 --- a/src/source/wave/FloatWaveSource.java +++ b/src/source/wave/FloatWaveSource.java @@ -29,7 +29,7 @@ import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.UnsupportedAudioFileException; -import sample.Listener; +import sample.simplex.SimplexSampleListener; public class FloatWaveSource extends WaveSource { @@ -44,7 +44,7 @@ public class FloatWaveSource extends WaveSource private int mBufferPointer = 0; private int mBufferLength; private ShortBuffer mBufferWrapper; - private Listener mListener; + private SimplexSampleListener mListener; private static int BUFFER_SAMPLE_SIZE = 2000; @@ -272,12 +272,12 @@ public class FloatWaveSource extends WaveSource /** * Adds a new listener to receive samples as they are read from the wave file */ - public void setListener( Listener listener ) + public void setListener( SimplexSampleListener listener ) { mListener = listener; } - public void removeListener( Listener listener ) + public void removeListener( SimplexSampleListener listener ) { mListener = null; } diff --git a/src/spectrum/ChannelSpectrumPanel.java b/src/spectrum/ChannelSpectrumPanel.java index 628e18cc..cca89602 100644 --- a/src/spectrum/ChannelSpectrumPanel.java +++ b/src/spectrum/ChannelSpectrumPanel.java @@ -17,6 +17,8 @@ import javax.swing.SwingUtilities; import net.miginfocom.swing.MigLayout; import sample.Listener; +import sample.complex.ComplexBuffer; +import sample.simplex.SimplexSampleListener; import settings.ColorSetting.ColorSettingName; import settings.ColorSettingMenuItem; import settings.Setting; @@ -43,7 +45,7 @@ import dsp.filter.Window.WindowType; public class ChannelSpectrumPanel extends JPanel implements ChannelEventListener, - Listener, + SimplexSampleListener, SettingChangeListener, SpectralDisplayAdjuster { @@ -62,7 +64,7 @@ public class ChannelSpectrumPanel extends JPanel private int mSpectrumAveragingSize; private int mSampleBufferSize = 2400; - private Float[] mSamples = new Float[ mSampleBufferSize ]; + private float[] mSamples = new float[ mSampleBufferSize ]; private int mSamplePointer = 0; private DecimatingSampleAssembler mDecimatingSampleAssembler; @@ -204,7 +206,7 @@ public class ChannelSpectrumPanel extends JPanel { if( mEnabled.get() && mCurrentChannel != null && mCurrentChannel.isProcessing() ) { - mCurrentChannel.getProcessingChain().addFloatListener( this ); + mCurrentChannel.getProcessingChain().addSimplexListener( (SimplexSampleListener)this ); mDFTProcessor.start(); } } @@ -214,7 +216,7 @@ public class ChannelSpectrumPanel extends JPanel if( mCurrentChannel != null && mCurrentChannel.isProcessing() ) { - mCurrentChannel.getProcessingChain().removeFloatListener( this ); + mCurrentChannel.getProcessingChain().removeSimplexListener( (SimplexSampleListener)this ); } mDFTProcessor.stop(); @@ -250,7 +252,7 @@ public class ChannelSpectrumPanel extends JPanel } @Override - public void receive( Float sample ) + public void receive( float sample ) { mDecimatingSampleAssembler.receive( sample ); } @@ -470,37 +472,36 @@ public class ChannelSpectrumPanel extends JPanel mSpectrumPanel.setBaseline( baseline ); } - public class DecimatingSampleAssembler implements Listener + public class DecimatingSampleAssembler { private FloatHalfBandFilter mDecimationFilter = new FloatHalfBandFilter( Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 ); private SampleAssembler mSampleAssembler; - public DecimatingSampleAssembler( Listener listener ) + public DecimatingSampleAssembler( Listener listener ) { mSampleAssembler = new SampleAssembler( listener ); mDecimationFilter.setListener( mSampleAssembler ); } - @Override - public void receive( Float sample ) + public void receive( float sample ) { mDecimationFilter.receive( sample ); } } - public class SampleAssembler implements Listener + public class SampleAssembler implements SimplexSampleListener { - private Listener mListener; + private Listener mListener; - public SampleAssembler( Listener listener ) + public SampleAssembler( Listener listener ) { mListener = listener; } @Override - public void receive( Float sample ) + public void receive( float sample ) { mSamples[ mSamplePointer++ ] = sample; @@ -508,15 +509,16 @@ public class ChannelSpectrumPanel extends JPanel { if( mEnabled.get() ) { - mListener.receive( Arrays.copyOf( mSamples, - mSamples.length ) ); + float[] copy = Arrays.copyOf( mSamples, mSamples.length ); + + mListener.receive( new ComplexBuffer( copy ) ); } mSamplePointer = 0; if( mSamples.length != mSampleBufferSize ) { - mSamples = new Float[ mSampleBufferSize ]; + mSamples = new float[ mSampleBufferSize ]; } } } diff --git a/src/spectrum/DFTProcessor.java b/src/spectrum/DFTProcessor.java index 934093cd..58d32f52 100644 --- a/src/spectrum/DFTProcessor.java +++ b/src/spectrum/DFTProcessor.java @@ -31,6 +31,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sample.Listener; +import sample.complex.ComplexBuffer; import source.Source; import source.Source.SampleType; import source.tuner.FrequencyChangeEvent; @@ -43,7 +44,7 @@ import dsp.filter.Window.WindowType; * Processes both complex samples or float samples and dispatches a float array * of DFT results, using configurable fft size and output dispatch timelines. */ -public class DFTProcessor implements Listener, +public class DFTProcessor implements Listener, FrequencyChangeListener { private final static Logger mLog = @@ -52,8 +53,8 @@ public class DFTProcessor implements Listener, private CopyOnWriteArrayList mListeners = new CopyOnWriteArrayList(); - private ArrayBlockingQueue mQueue = - new ArrayBlockingQueue( 200 ); + private ArrayBlockingQueue mQueue = + new ArrayBlockingQueue( 200 ); private ScheduledExecutorService mScheduler = Executors.newScheduledThreadPool(1); @@ -74,7 +75,7 @@ public class DFTProcessor implements Listener, private float mNewFloatResidual; private float[] mPreviousFrame = new float[ 8192 ]; - private Float[] mCurrentBuffer; + private float[] mCurrentBuffer; private int mCurrentBufferPointer = 0; private SampleType mSampleType; @@ -222,16 +223,16 @@ public class DFTProcessor implements Listener, * Places the sample into a transfer queue for future processing. */ @Override - public void receive( Float[] samples ) + public void receive( ComplexBuffer sampleBuffer ) { - if( !mQueue.offer( samples ) ) + if( !mQueue.offer( sampleBuffer ) ) { mLog.error( "DFTProcessor - [" + mSampleType.toString() + "]queue is full, purging queue, " - + "samples[" + samples + "]" ); + + "samples[" + sampleBuffer + "]" ); mQueue.clear(); - mQueue.offer( samples ); + mQueue.offer( sampleBuffer ); } } @@ -241,7 +242,8 @@ public class DFTProcessor implements Listener, try { - mCurrentBuffer = mQueue.take(); + ComplexBuffer buffer = mQueue.take(); + mCurrentBuffer = buffer.getSamples(); } catch ( InterruptedException e ) { diff --git a/src/spectrum/SpectralDisplayPanel.java b/src/spectrum/SpectralDisplayPanel.java index 817a7bac..692e4205 100644 --- a/src/spectrum/SpectralDisplayPanel.java +++ b/src/spectrum/SpectralDisplayPanel.java @@ -47,6 +47,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sample.Listener; +import sample.complex.ComplexBuffer; import settings.ColorSetting; import settings.ColorSetting.ColorSettingName; import settings.ColorSettingMenuItem; @@ -89,7 +90,7 @@ import dsp.filter.Window.WindowType; */ public class SpectralDisplayPanel extends JPanel implements ChannelEventListener, - Listener, + Listener, FrequencyChangeListener, SettingChangeListener, TunerSelectionListener @@ -359,9 +360,9 @@ public class SpectralDisplayPanel extends JPanel } @Override - public void receive( Float[] samples ) + public void receive( ComplexBuffer sampleBuffer ) { - mDFTProcessor.receive( samples ); + mDFTProcessor.receive( sampleBuffer ); } /** @@ -382,7 +383,7 @@ public class SpectralDisplayPanel extends JPanel mTuner.addListener( (FrequencyChangeListener)this ); //Register the dft processor to receive samples from the tuner - mTuner.addListener( (Listener)mDFTProcessor ); + mTuner.addListener( (Listener)mDFTProcessor ); //Fire frequency and sample rate change events so that everyone //can init @@ -413,7 +414,7 @@ public class SpectralDisplayPanel extends JPanel mTuner.removeListener( (FrequencyChangeListener)this ); //Deregister the dft processor from receiving samples - mTuner.removeListener( (Listener)mDFTProcessor ); + mTuner.removeListener( (Listener)mDFTProcessor ); mTuner = null; } }