From ae10b5fc1c600cf762bd96344ed68b5966ecc871 Mon Sep 17 00:00:00 2001 From: denny Date: Sun, 29 Nov 2015 07:11:47 -0500 Subject: [PATCH 01/20] Airspy controller - changes method for adjusting center tuned frequency when tuner channels are allocated by the airspy. New method determines the min/max requested frequency range and sets tuner center to the center of that range, while limiting the total usable bandwidth to 90% of the bandwidth, to account for rolloff of 5% at the upper/lower edges. When a new channel is requested, if the channel fits within the current center frequency and usable bandwidth, the center tune frequency is not adjusted, to minimize the number of retunes on a busy system. --- src/controller/channel/Channel.java | 2 +- src/source/tuner/TunerController.java | 2 +- .../tuner/airspy/AirspyTunerController.java | 44 ++++++++++++++++--- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/controller/channel/Channel.java b/src/controller/channel/Channel.java index 234ecdfe..0ac11a9e 100644 --- a/src/controller/channel/Channel.java +++ b/src/controller/channel/Channel.java @@ -714,7 +714,7 @@ public class Channel extends Configuration } catch( SourceException se ) { - mLog.info( "Couldn't obtain source for channel [" + mName + "]" ); + //Do nothing, we couldn't get a source for the channel } } } diff --git a/src/source/tuner/TunerController.java b/src/source/tuner/TunerController.java index 7b1aaea3..0676e4f6 100644 --- a/src/source/tuner/TunerController.java +++ b/src/source/tuner/TunerController.java @@ -114,7 +114,7 @@ public abstract class TunerController implements Tunable public long getMinTunedFrequency() { - return mFrequencyController.getFrequency() - + return mFrequencyController.getFrequency() - ( mFrequencyController.getBandwidth() / 2 ); } diff --git a/src/source/tuner/airspy/AirspyTunerController.java b/src/source/tuner/airspy/AirspyTunerController.java index 97d4098b..16fb9489 100644 --- a/src/source/tuner/airspy/AirspyTunerController.java +++ b/src/source/tuner/airspy/AirspyTunerController.java @@ -832,7 +832,7 @@ public class AirspyTunerController extends TunerController } /** - * Indicates if the tuner can accomodate this new channel frequency and + * Indicates if the tuner can accommodate this new channel frequency and * bandwidth, along with all of the existing tuned channels currently in * place. */ @@ -857,20 +857,49 @@ public class AirspyTunerController extends TunerController { return true; } + + int usable = getUsableBandwidth(); if( channel.getMinFrequency() < minLockedFrequency ) { - return ( maxLockedFrequency - channel.getMinFrequency() ) < getBandwidth() - 1E5; + return ( maxLockedFrequency - channel.getMinFrequency() ) <= usable; } if( channel.getMaxFrequency() > maxLockedFrequency ) { - return ( channel.getMaxFrequency() - minLockedFrequency ) <= getBandwidth() - 1E5; + return ( channel.getMaxFrequency() - minLockedFrequency ) <= usable; } } return false; } + + /** + * Limits the usable bandwidth of the tuner to 90% of the bandwidth to + * account for roll-off at the band edges + */ + private int getUsableBandwidth() + { + return (int)( (double)getBandwidth() * 0.9d ); + } + + /** + * Overrides the default tuner controller method to limit the total bandwidth + * of the tuner to 90% and adjust the min tuned frequency accordingly + */ + public long getMinTunedFrequency() + { + return mFrequencyController.getFrequency() - ( getUsableBandwidth() / 2 ); + } + + /** + * Overrides the default tuner controller method to limit the total bandwidth + * of the tuner to 90% and adjust the max tuned frequency accordingly + */ + public long getMaxTunedFrequency() + { + return mFrequencyController.getFrequency() + ( getUsableBandwidth() / 2 ); + } public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager, Tuner tuner, TunerChannel tunerChannel ) @@ -886,11 +915,12 @@ public class AirspyTunerController extends TunerController long min = mTunedChannels.get( 0 ).getMinFrequency(); long max = mTunedChannels.get( mTunedChannels.size() - 1 ).getMaxFrequency(); - - if( !( getMinTunedFrequency() <= min && - max <= getMaxTunedFrequency() ) ) + + /* Adjust the center frequency if the set of tuner channels don't + * currently fit within the usable bandwidth and current center frequency */ + if( !( getMinTunedFrequency() <= min && max <= getMaxTunedFrequency() ) ) { - long freq = min + ( mFrequencyController.getBandwidth() / 2 ) + 50000; + long freq = min + ( ( max - min ) / 2 ); mFrequencyController.setFrequency( freq ); } From 82a715596659dc9a46f2d33a7ae3ed51ebe86a51 Mon Sep 17 00:00:00 2001 From: denny Date: Sun, 29 Nov 2015 07:14:21 -0500 Subject: [PATCH 02/20] Adjusted color gradient used in spectral display and some code cleanup. --- .../ConfigurationControllerModel.java | 3 ++ .../demodulate/fm/FMDemodulatorModule.java | 5 ++- src/spectrum/ChannelSpectrumPanel.java | 33 ------------------- src/spectrum/OverlayPanel.java | 12 +++++-- src/spectrum/SpectrumPanel.java | 2 +- 5 files changed, 17 insertions(+), 38 deletions(-) diff --git a/src/controller/ConfigurationControllerModel.java b/src/controller/ConfigurationControllerModel.java index 8b20b272..9ace2ec2 100644 --- a/src/controller/ConfigurationControllerModel.java +++ b/src/controller/ConfigurationControllerModel.java @@ -39,6 +39,8 @@ import source.tuner.TunerGroupNode; import source.tuner.TunerNode; import source.tuner.TunerSelectionListener; import controller.channel.Channel; +import controller.channel.ChannelEvent; +import controller.channel.ChannelEvent.Event; import controller.channel.ChannelNode; import controller.site.Site; import controller.system.System; @@ -183,6 +185,7 @@ public class ConfigurationControllerModel extends DefaultTreeModel SourceConfigTuner sourceConfig = new SourceConfigTuner(); sourceConfig.setFrequency( frequency ); channel.setSourceConfiguration( sourceConfig ); + mResourceManager.getChannelManager().channelChanged( new ChannelEvent( channel, Event.CHANNEL_ADDED ) ); channel.setEnabled( true, true ); site.addChannel( channel ); diff --git a/src/module/demodulate/fm/FMDemodulatorModule.java b/src/module/demodulate/fm/FMDemodulatorModule.java index 871e2d0c..2d2afe16 100644 --- a/src/module/demodulate/fm/FMDemodulatorModule.java +++ b/src/module/demodulate/fm/FMDemodulatorModule.java @@ -133,7 +133,10 @@ public class FMDemodulatorModule extends Module implements IComplexBufferListene mDemodulator.dispose(); mDemodulator = null; - mDCRemovalFilter.dispose(); + if( mDCRemovalFilter != null ) + { + mDCRemovalFilter.dispose(); + } mDCRemovalFilter = null; if( mAutomaticFrequencyControl != null ) diff --git a/src/spectrum/ChannelSpectrumPanel.java b/src/spectrum/ChannelSpectrumPanel.java index a3bd460a..40bd0d99 100644 --- a/src/spectrum/ChannelSpectrumPanel.java +++ b/src/spectrum/ChannelSpectrumPanel.java @@ -492,37 +492,4 @@ public class ChannelSpectrumPanel extends JPanel { mSpectrumPanel.setSmoothingType( type ); } - - public class SampleAssembler implements RealSampleListener - { - private Listener mListener; - - public SampleAssembler( Listener listener ) - { - mListener = listener; - } - - @Override - public void receive( float sample ) - { - mSamples[ mSamplePointer++ ] = sample; - - if( mSamplePointer >= mSamples.length ) - { - if( mEnabled.get() ) - { - float[] copy = Arrays.copyOf( mSamples, mSamples.length ); - - mListener.receive( new ComplexBuffer( copy ) ); - } - - mSamplePointer = 0; - - if( mSamples.length != mSampleBufferSize ) - { - mSamples = new float[ mSampleBufferSize ]; - } - } - } - } } diff --git a/src/spectrum/OverlayPanel.java b/src/spectrum/OverlayPanel.java index d70b22d8..1b10bf59 100644 --- a/src/spectrum/OverlayPanel.java +++ b/src/spectrum/OverlayPanel.java @@ -75,7 +75,7 @@ public class OverlayPanel extends JPanel } private final static BasicStroke DASHED_STROKE = new BasicStroke(0.8f, - BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 5.0f, new float[] {5.0f}, 0.0f); + BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 5.0f, new float[] {2.0f, 4.0f}, 0.0f); private static DecimalFormat CURSOR_FORMAT = new DecimalFormat( "000.00000" ); private long mFrequency = 0; @@ -342,7 +342,7 @@ public class OverlayPanel extends JPanel { Stroke currentStroke = graphics.getStroke(); - graphics.setStroke( DASHED_STROKE ); +// graphics.setStroke( DASHED_STROKE ); long minFrequency = getMinDisplayFrequency(); long maxFrequency = getMaxDisplayFrequency(); @@ -352,6 +352,12 @@ public class OverlayPanel extends JPanel int major = mLabelSizeMonitor.getMajorTickIncrement( graphics ); int minor = mLabelSizeMonitor.getMinorTickIncrement( graphics ); + //Avoid divide by zero error + if( minor == 0 ) + { + minor = 1; + } + //Adjust the start frequency to a multiple of the minor tick spacing long frequency = minFrequency - ( minFrequency % minor ); @@ -373,7 +379,7 @@ public class OverlayPanel extends JPanel frequency += minor; } - graphics.setStroke( currentStroke ); +// graphics.setStroke( currentStroke ); } /** diff --git a/src/spectrum/SpectrumPanel.java b/src/spectrum/SpectrumPanel.java index a272e3b1..d2295cbe 100644 --- a/src/spectrum/SpectrumPanel.java +++ b/src/spectrum/SpectrumPanel.java @@ -189,7 +189,7 @@ public class SpectrumPanel extends JPanel //Define the gradient GradientPaint gradient = new GradientPaint( 0, - getSize().height / 4, + ( getSize().height - mSpectrumInset ) / 2, mColorSpectrumGradientTop, 0, getSize().height, From ebb59f5181155aa27995d48141bb0bb86a3f46a9 Mon Sep 17 00:00:00 2001 From: denny Date: Sun, 29 Nov 2015 08:46:03 -0500 Subject: [PATCH 03/20] AM Decoder adapted to the new processing chain architecture. --- src/dsp/gain/ComplexAutomaticGainControl.java | 4 ++-- src/module/decode/DecoderFactory.java | 3 +++ src/module/decode/am/AMDecoder.java | 2 -- src/module/decode/am/AMDecoderPanel.java | 6 ++++++ src/module/demodulate/am/AMDemodulatorModule.java | 2 +- 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/dsp/gain/ComplexAutomaticGainControl.java b/src/dsp/gain/ComplexAutomaticGainControl.java index 4ecbb2ad..c5896016 100644 --- a/src/dsp/gain/ComplexAutomaticGainControl.java +++ b/src/dsp/gain/ComplexAutomaticGainControl.java @@ -105,8 +105,8 @@ public class ComplexAutomaticGainControl implements Listener, private static final double AGC_OUT_SCALE = 0.7; /* Keep max input and output the same */ - private static final double MAX_AMPLITUDE = 32767.0; //1.0; - private static final double MAX_MANUAL_AMPLITUDE = 32767.0; //1.0; + private static final double MAX_AMPLITUDE = 1.0; + private static final double MAX_MANUAL_AMPLITUDE = 1.0; /* Specifies AGC manual gain in dB if AGC is not active ( 0 to 100 dB) */ private static final double MANUAL_GAIN = 0.0; diff --git a/src/module/decode/DecoderFactory.java b/src/module/decode/DecoderFactory.java index 4eed26cd..459aad25 100644 --- a/src/module/decode/DecoderFactory.java +++ b/src/module/decode/DecoderFactory.java @@ -83,6 +83,7 @@ import module.decode.state.TrafficChannelManager; import module.decode.tait.Tait1200Decoder; import module.decode.tait.Tait1200DecoderPanel; import module.decode.tait.Tait1200DecoderState; +import module.demodulate.am.AMDemodulatorModule; import module.demodulate.fm.FMDemodulatorModule; import org.slf4j.Logger; @@ -192,8 +193,10 @@ public class DecoderFactory switch( decodeConfig.getDecoderType() ) { case AM: + modules.add( new AMDemodulatorModule() ); modules.add( new AMDecoder( decodeConfig ) ); modules.add( new AlwaysUnsquelchedDecoderState( DecoderType.AM, channelName ) ); + modules.add( new AudioModule( recordAudio, REMOVE_DC ) ); break; case NBFM: modules.add( new AudioModule( recordAudio, NO_REMOVE_DC ) ); diff --git a/src/module/decode/am/AMDecoder.java b/src/module/decode/am/AMDecoder.java index abb99314..8b407b67 100644 --- a/src/module/decode/am/AMDecoder.java +++ b/src/module/decode/am/AMDecoder.java @@ -27,8 +27,6 @@ public class AMDecoder extends PrimaryDecoder public AMDecoder( DecodeConfiguration config ) { super( config ); - - //TODO: wire in the am decoder pieces } @Override diff --git a/src/module/decode/am/AMDecoderPanel.java b/src/module/decode/am/AMDecoderPanel.java index 0e97505e..cae8326d 100644 --- a/src/module/decode/am/AMDecoderPanel.java +++ b/src/module/decode/am/AMDecoderPanel.java @@ -19,6 +19,7 @@ package module.decode.am; import javax.swing.JLabel; +import net.miginfocom.swing.MigLayout; import module.decode.state.ChangedAttribute; import module.decode.state.DecoderPanel; import module.decode.state.DecoderState; @@ -41,7 +42,12 @@ public class AMDecoderPanel extends DecoderPanel public void init() { + super.init(); + + setLayout( new MigLayout( "insets 0 0 0 0", "[grow,fill]", "[]0[]0[]") ); + mProtocol.setFont( mFontDecoder ); + mProtocol.setForeground( mColorLabelDecoder ); add( mProtocol, "wrap" ); } diff --git a/src/module/demodulate/am/AMDemodulatorModule.java b/src/module/demodulate/am/AMDemodulatorModule.java index d9a77292..716c6677 100644 --- a/src/module/demodulate/am/AMDemodulatorModule.java +++ b/src/module/demodulate/am/AMDemodulatorModule.java @@ -58,7 +58,7 @@ public class AMDemodulatorModule extends Module public AMDemodulatorModule() { mIQFilter = new ComplexFIRFilter_CB_CB( FilterFactory.getLowPass( - SAMPLE_RATE, 6500, 73, WindowType.HAMMING ), 1.0f ); + SAMPLE_RATE, 5000, 73, WindowType.HAMMING ), 1.0f ); mDemodulator = new AMDemodulator_CB( 500.0f ); mIQFilter.setListener( mDemodulator ); From ed91c617d9fb9d04390faf0668dff79d5bc041ab Mon Sep 17 00:00:00 2001 From: denny Date: Mon, 30 Nov 2015 19:59:06 -0500 Subject: [PATCH 04/20] Updates to P25-C4FM decoder filtering and frequency correction. --- src/module/decode/p25/C4FMSymbolFilter.java | 3 --- src/module/decode/p25/P25_C4FMDecoder.java | 7 +++++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/module/decode/p25/C4FMSymbolFilter.java b/src/module/decode/p25/C4FMSymbolFilter.java index 6983d28a..17be10ac 100644 --- a/src/module/decode/p25/C4FMSymbolFilter.java +++ b/src/module/decode/p25/C4FMSymbolFilter.java @@ -260,9 +260,6 @@ public class C4FMSymbolFilter implements FrequencyCorrectionResetListener, // .getErrorCorrection() ); mFrequencyAdjustmentRequested = 0; - - /* reset internal frequency error tracker */ - mCoarseFrequencyCorrection = 0; } } diff --git a/src/module/decode/p25/P25_C4FMDecoder.java b/src/module/decode/p25/P25_C4FMDecoder.java index 75f1669b..1bd7f486 100644 --- a/src/module/decode/p25/P25_C4FMDecoder.java +++ b/src/module/decode/p25/P25_C4FMDecoder.java @@ -24,6 +24,7 @@ import instrument.tap.stream.FloatBufferTap; import instrument.tap.stream.FloatTap; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.slf4j.Logger; @@ -74,8 +75,10 @@ public class P25_C4FMDecoder extends P25Decoder implements IRealBufferListener super( aliasList ); /* Filter demodulated sample buffers */ - mC4FMPreFilter = new RealFIRFilter_RB_RB( FilterFactory.getLowPass( - 48000, 6750, 7500, 60, WindowType.HANNING, true ), 1.0f ); + float[] filter = FilterFactory.getLowPass( 48000, 2500, 4000, 80, WindowType.HANNING, true ); + + mLog.debug( "Filter taps:" + filter.length + " co:" + Arrays.toString( filter ) ); + mC4FMPreFilter = new RealFIRFilter_RB_RB( filter, 1.0f ); /* Issue tuned frequency correction commands, remotely controlled by the * downstream symbol filter */ From c97185d650f837318c8e8ae4d6890f6fa924742b Mon Sep 17 00:00:00 2001 From: denny Date: Mon, 30 Nov 2015 20:21:41 -0500 Subject: [PATCH 05/20] Updated change log --- CHANGELOG | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c05ad2f8..b2db83db 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,15 +1,22 @@ -0.2.0 2015xxxx Major overhaul of underlying processing chain and extensive - updates to all decoders and filters - - Resolved issue with memory leaks and crashes - - New audio playback interface with global mute and support for one or two - audio channels - - Audio playback is prioritized using a new Call Priority alias action - with selected channels having the highest priority and any audio without - an alias identifier having the lowest priority. - - Audio recording by call with new 'Non-Recordable' alias action - - (planned)Added Ettus UHD support for B100 with WBX tuner - - Added Airspy tuner support +0.2.0 2015xxxx + - Major overhaul of underlying processing chain and extensive updates to all + decoders and filters + - New audio playback interface with global mute and support for one or two + audio channels + - Audio playback is prioritized using a new Call Priority alias action + with selected channels having the highest priority and any audio without + an alias identifier having the lowest priority. + - Updated Audio recording per call with new 'Non-Recordable' alias action + - Updated Baseband (I/Q) recording at 48 kHz + - Added Airspy tuner support + - New traffic channel pool manager for trunked call following + - P25 Decoder for C4FM and LSM with trunked call following and audio + decoding via the JMBE library + - Updated MPT1327 decoder and trunked call following. + - Updated spectral and waterfall display with zoom (2-64x) and pan and automatic + noise floor (dB) adjusted to tuner sample size + - Resolved some issues with memory leaks and crashes 0.1.5 20150612 Added initial P25 Decoder support with integrated audio decoding using the JMBE library From 36880122afd1b64363c0daad619b5685e3f0ecee Mon Sep 17 00:00:00 2001 From: denny Date: Tue, 1 Dec 2015 20:39:12 -0500 Subject: [PATCH 06/20] Adds keyboard accelerator (Alt-c) for screen capture --- src/gui/SDRTrunk.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/gui/SDRTrunk.java b/src/gui/SDRTrunk.java index 8854c9de..1eae1b7b 100644 --- a/src/gui/SDRTrunk.java +++ b/src/gui/SDRTrunk.java @@ -24,6 +24,7 @@ import java.awt.EventQueue; import java.awt.Robot; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; import java.awt.image.BufferedImage; import java.io.BufferedReader; import java.io.IOException; @@ -39,6 +40,7 @@ import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JSeparator; +import javax.swing.KeyStroke; import map.MapService; import net.miginfocom.swing.MigLayout; @@ -258,6 +260,11 @@ public class SDRTrunk fileMenu.add( exitMenu ); JMenuItem screenCaptureItem = new JMenuItem( "Screen Capture" ); + + screenCaptureItem.setMnemonic( KeyEvent.VK_C ); + screenCaptureItem.setAccelerator( + KeyStroke.getKeyStroke( KeyEvent.VK_C, ActionEvent.ALT_MASK ) ); + screenCaptureItem.addActionListener( new ActionListener() { @Override From d9fe979ccf17d4b1451cfd439ff3a6e4131e539d Mon Sep 17 00:00:00 2001 From: denny Date: Wed, 2 Dec 2015 19:10:38 -0500 Subject: [PATCH 07/20] Resolves issue with residual buffer decimation processing after dispose is invoked on call teardown. --- src/source/tuner/TunerChannelSource.java | 91 ++++++++++++------------ 1 file changed, 46 insertions(+), 45 deletions(-) diff --git a/src/source/tuner/TunerChannelSource.java b/src/source/tuner/TunerChannelSource.java index ea3534f6..4d7d3642 100644 --- a/src/source/tuner/TunerChannelSource.java +++ b/src/source/tuner/TunerChannelSource.java @@ -264,6 +264,7 @@ public class TunerChannelSource extends ComplexSource public class DecimationProcessor implements Runnable { private AtomicBoolean mDispose = new AtomicBoolean( false ); + private List mSampleBuffers = new ArrayList(); public void dispose() { @@ -278,64 +279,64 @@ public class TunerChannelSource extends ComplexSource * run the program out of memory */ try { - /* Check to see if we've been shutdown */ - if( mDispose.get() ) + if( mBuffer != null ) { - cleanup(); - return; - } - else - { - List sampleBuffers = - new ArrayList(); - - if( mBuffer != null ) + mBuffer.drainTo( mSampleBuffers, 4 ); + + for( Buffer buffer: mSampleBuffers ) { - mBuffer.drainTo( sampleBuffers, 4 ); - - for( Buffer buffer: sampleBuffers ) + /* Check to see if we've been shutdown */ + if( mDispose.get() ) { - /* Check to see if we've been shutdown */ - if( mDispose.get() ) - { - cleanup(); - return; - } - else - { - float[] samples = buffer.getSamples(); + cleanup(); + return; + } + else + { + float[] samples = buffer.getSamples(); - /* We make a copy of the buffer so that we don't affect - * anyone else that is using the same buffer, like other - * channels or the spectral display */ - float[] translated = new float[ samples.length ]; + /* We make a copy of the buffer so that we don't affect + * anyone else that is using the same buffer, like other + * channels or the spectral display */ + float[] translated = new float[ samples.length ]; + + /* Perform frequency translation */ + for( int x = 0; x < samples.length; x += 2 ) + { + mMixer.rotate(); - /* Perform frequency translation */ - for( int x = 0; x < samples.length; x += 2 ) - { - mMixer.rotate(); - - translated[ x ] = Complex.multiplyInphase( + translated[ x ] = Complex.multiplyInphase( + samples[ x ], samples[ x + 1 ], mMixer.inphase(), mMixer.quadrature() ); + + translated[ x + 1 ] = Complex.multiplyQuadrature( samples[ x ], samples[ x + 1 ], mMixer.inphase(), mMixer.quadrature() ); - - translated[ x + 1 ] = Complex.multiplyQuadrature( - samples[ x ], samples[ x + 1 ], mMixer.inphase(), mMixer.quadrature() ); - } - - if( mDecimationFilter != null ) - { - mDecimationFilter.receive( new ComplexBuffer( translated ) ); - } + } + + if( mDecimationFilter != null ) + { + mDecimationFilter.receive( new ComplexBuffer( translated ) ); } } - - sampleBuffers.clear(); } + + mSampleBuffers.clear(); } } catch( Exception e ) { - mLog.error( "encountered an error during decimation process", e ); + /* Only log the stack trace if we've haven't been shutdown and + * this is a true error */ + if( !mDispose.get() ) + { + mLog.error( "Error encountered during decimation process", e ); + } + } + + /* Check to see if we've been shutdown */ + if( mDispose.get() ) + { + mSampleBuffers.clear(); + cleanup(); } } } From 6763a4bdd612dcaabb633901c3af8c607851cb50 Mon Sep 17 00:00:00 2001 From: denny Date: Fri, 11 Dec 2015 13:34:20 -0500 Subject: [PATCH 08/20] Adds de-emphasis filter to P25 decoded IMBE audio output. --- src/audio/output/StereoAudioOutput.java | 3 +- src/buffer/FloatCircularBuffer.java | 5 ++ src/dsp/filter/iir/DeemphasisFilter.java | 60 +++++++++++++++++++ src/module/decode/p25/P25DecoderPanel.java | 4 +- .../decode/p25/audio/P25AudioModule.java | 21 ++++--- src/source/wave/RealWaveSource.java | 8 ++- 6 files changed, 88 insertions(+), 13 deletions(-) create mode 100644 src/dsp/filter/iir/DeemphasisFilter.java diff --git a/src/audio/output/StereoAudioOutput.java b/src/audio/output/StereoAudioOutput.java index 2a7b9dad..a8182359 100644 --- a/src/audio/output/StereoAudioOutput.java +++ b/src/audio/output/StereoAudioOutput.java @@ -95,7 +95,8 @@ public class StereoAudioOutput extends AudioOutput catch ( LineUnavailableException e ) { mLog.error( "Couldn't obtain source data line for 48kHz PCM stereo " - + "audio output" ); + + "audio output - mixer [" + mixer.getMixerInfo().getName() + + "] channel [" + mMixerChannel.name() + "]" ); } } diff --git a/src/buffer/FloatCircularBuffer.java b/src/buffer/FloatCircularBuffer.java index 0fa3ad79..ccb749e1 100644 --- a/src/buffer/FloatCircularBuffer.java +++ b/src/buffer/FloatCircularBuffer.java @@ -40,6 +40,11 @@ public class FloatCircularBuffer Arrays.fill( mBuffer, (float)0.0 ); } + public int getSize() + { + return mBuffer.length; + } + public float[] getBuffer() { return mBuffer; diff --git a/src/dsp/filter/iir/DeemphasisFilter.java b/src/dsp/filter/iir/DeemphasisFilter.java new file mode 100644 index 00000000..9ba94b70 --- /dev/null +++ b/src/dsp/filter/iir/DeemphasisFilter.java @@ -0,0 +1,60 @@ +package dsp.filter.iir; + +import sample.real.RealBuffer; + +public class DeemphasisFilter +{ + private static final float MAX_SAMPLE_VALUE = 0.95f; + private float mAlpha; + private float mGain; + private float mPrevious = 0.0f; + + public DeemphasisFilter( float sampleRate, float cutoff, float gain ) + { + mAlpha = (float)Math.exp( -2.0 * Math.PI * cutoff * ( 1.0 / sampleRate ) ); + mGain = gain; + } + + public float filter( float sample ) + { + mPrevious = sample + ( mAlpha * mPrevious ); + + return declip( mPrevious * mGain ); + } + + private float declip( float value ) + { + if( value > MAX_SAMPLE_VALUE ) + { + return MAX_SAMPLE_VALUE; + } + else if( value < -MAX_SAMPLE_VALUE ) + { + return -MAX_SAMPLE_VALUE; + } + + else return value; + } + + public float[] filter( float[] samples ) + { + for( int x = 0; x < samples.length; x++ ) + { + samples[ x ] = filter( samples[ x ] ); + } + + return samples; + } + + public RealBuffer filter( RealBuffer buffer ) + { + float[] samples = buffer.getSamples(); + + for( int x = 0; x < samples.length; x++ ) + { + samples[ x ] = filter( samples[ x ] ); + } + + return buffer; + } +} diff --git a/src/module/decode/p25/P25DecoderPanel.java b/src/module/decode/p25/P25DecoderPanel.java index 15520e0b..ea1dd648 100644 --- a/src/module/decode/p25/P25DecoderPanel.java +++ b/src/module/decode/p25/P25DecoderPanel.java @@ -149,7 +149,7 @@ public class P25DecoderPanel extends DecoderPanel { mFromAlias.setText( from.getName() ); - String icon = from.getIconName(); + final String icon = from.getIconName(); if( icon != null ) { @@ -180,7 +180,7 @@ public class P25DecoderPanel extends DecoderPanel { mToAlias.setText( to.getName() ); - String icon = to.getIconName(); + final String icon = to.getIconName(); if( icon != null ) { diff --git a/src/module/decode/p25/audio/P25AudioModule.java b/src/module/decode/p25/audio/P25AudioModule.java index 3d3f0d9b..cdf5ea0a 100644 --- a/src/module/decode/p25/audio/P25AudioModule.java +++ b/src/module/decode/p25/audio/P25AudioModule.java @@ -6,8 +6,6 @@ import message.IMessageListener; import message.Message; import module.Module; import module.decode.p25.message.ldu.LDUMessage; -import module.decode.p25.message.tdu.TDUMessage; -import module.decode.p25.message.tdu.lc.TDULinkControlMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,6 +21,8 @@ import audio.squelch.ISquelchStateListener; import audio.squelch.SquelchState; import controller.channel.ChannelEvent; import controller.channel.IChannelEventListener; +import dsp.audio.P25AGC; +import dsp.filter.iir.DeemphasisFilter; public class P25AudioModule extends Module implements Listener, IAudioPacketProvider, IMessageListener, IChannelEventListener, @@ -43,6 +43,8 @@ public class P25AudioModule extends Module implements Listener, private AudioMetadata mAudioMetadata; private ChannelEventListener mChannelEventListener = new ChannelEventListener(); private SquelchStateListener mSquelchStateListener = new SquelchStateListener(); +// private P25AGC mAGC = new P25AGC( 2.0, 10.0 ); + private DeemphasisFilter mDeemphasis = new DeemphasisFilter( 48000.0f, 300.0f, 0.6f ); public P25AudioModule( boolean record ) { @@ -89,9 +91,10 @@ public class P25AudioModule extends Module implements Listener, } /** - * Primary inject point for p25 imbe audio frame messages. Each LDU audio - * message contains 9 imbe voice frames. If the audio is not encrypted, - * we insert each audio frame asynchronously into the audio converter. + * Primary processing method for p25 imbe audio frame messages. Each LDU + * audio message contains 9 imbe voice frames. Each frame is converted to + * audio when the JMBE library is loaded, processed by auto-gain, and + * broadcast to the audio packet listener. */ public void receive( Message message ) { @@ -101,15 +104,15 @@ public class P25AudioModule extends Module implements Listener, { LDUMessage ldu = (LDUMessage)message; - /* If we detect a valid LDU message with the encryption flag - * set to true, we toggle the encrypted audio state until we - * receive the first TDU or TDULC message, then reset it. */ + /* Only process unencrypted audio frames */ if( !( ldu.isValid() && ldu.isEncrypted() ) ) { - for( byte[] frame: ((LDUMessage)message).getIMBEFrames() ) + for( byte[] frame: ldu.getIMBEFrames() ) { float[] audio = mAudioConverter.decode( frame ); + audio = mDeemphasis.filter( audio ); + mAudioPacketListener.receive( new AudioPacket( audio, mAudioMetadata.copyOf() ) ); } diff --git a/src/source/wave/RealWaveSource.java b/src/source/wave/RealWaveSource.java index b801b73a..a7251674 100644 --- a/src/source/wave/RealWaveSource.java +++ b/src/source/wave/RealWaveSource.java @@ -36,7 +36,8 @@ import source.IControllableFileSource; import source.IFrameLocationListener; import source.RealSource; -public class RealWaveSource extends RealSource implements IControllableFileSource +public class RealWaveSource extends RealSource + implements IControllableFileSource, AutoCloseable { private final static Logger mLog = LoggerFactory.getLogger( RealWaveSource.class ); @@ -168,6 +169,11 @@ public class RealWaveSource extends RealSource implements IControllableFileSourc { if( samplesRead < buffer.length ) { + if( samplesRead == -1 ) + { + throw new IOException( "End of recording" ); + } + buffer = Arrays.copyOf( buffer, samplesRead ); } From ee96ce43ad1aca450cd45a88bbb4cb966dd75e16 Mon Sep 17 00:00:00 2001 From: denny Date: Fri, 11 Dec 2015 13:35:47 -0500 Subject: [PATCH 09/20] Fixed import in P25 audio module --- src/module/decode/p25/audio/P25AudioModule.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/module/decode/p25/audio/P25AudioModule.java b/src/module/decode/p25/audio/P25AudioModule.java index cdf5ea0a..c9074928 100644 --- a/src/module/decode/p25/audio/P25AudioModule.java +++ b/src/module/decode/p25/audio/P25AudioModule.java @@ -21,7 +21,6 @@ import audio.squelch.ISquelchStateListener; import audio.squelch.SquelchState; import controller.channel.ChannelEvent; import controller.channel.IChannelEventListener; -import dsp.audio.P25AGC; import dsp.filter.iir.DeemphasisFilter; public class P25AudioModule extends Module implements Listener, @@ -43,7 +42,6 @@ public class P25AudioModule extends Module implements Listener, private AudioMetadata mAudioMetadata; private ChannelEventListener mChannelEventListener = new ChannelEventListener(); private SquelchStateListener mSquelchStateListener = new SquelchStateListener(); -// private P25AGC mAGC = new P25AGC( 2.0, 10.0 ); private DeemphasisFilter mDeemphasis = new DeemphasisFilter( 48000.0f, 300.0f, 0.6f ); public P25AudioModule( boolean record ) From 98c70a1b12e97d650f9d91184f11db2285cb464e Mon Sep 17 00:00:00 2001 From: denny Date: Fri, 11 Dec 2015 21:33:20 -0500 Subject: [PATCH 10/20] Resolved issue with wave recorders not shutting down correctly, causing errors with max file handles is exceeded. --- src/record/RecorderManager.java | 4 ++-- src/record/wave/RealBufferWaveRecorder.java | 15 +++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/record/RecorderManager.java b/src/record/RecorderManager.java index e247036b..b9941926 100644 --- a/src/record/RecorderManager.java +++ b/src/record/RecorderManager.java @@ -72,8 +72,8 @@ public class RecorderManager implements Listener } else if( audioPacket.getType() == AudioPacket.Type.END ) { - mRecorders.remove( identifier ); - recorder.stop(); + RealBufferWaveRecorder finished = mRecorders.remove( identifier ); + finished.stop(); } } else if( audioPacket.getType() == AudioPacket.Type.AUDIO ) diff --git a/src/record/wave/RealBufferWaveRecorder.java b/src/record/wave/RealBufferWaveRecorder.java index 5de38587..49b0f3b6 100644 --- a/src/record/wave/RealBufferWaveRecorder.java +++ b/src/record/wave/RealBufferWaveRecorder.java @@ -125,11 +125,10 @@ public class RealBufferWaveRecorder extends Module public void stop() { - if( mRunning.compareAndSet( true, false ) ) - { - /* Signal the buffer processor to end with a poison pill */ - receive( new PoisonPill() ); - } + /* Signal the buffer processor to end with a poison pill */ + receive( new PoisonPill() ); + + mRunning.set( false ); } @Override @@ -191,7 +190,6 @@ public class RealBufferWaveRecorder extends Module try { mWriter.close(); - mWriter = null; } catch( IOException io ) { @@ -200,11 +198,12 @@ public class RealBufferWaveRecorder extends Module } } - mFile = null; - mThreadPoolManager.cancel( mProcessorHandle ); + mWriter = null; + mFile = null; mProcessorHandle = null; + mThreadPoolManager = null; } else { From 4caecaa476b995c0d209b2116a26250da3d6fbb2 Mon Sep 17 00:00:00 2001 From: denny Date: Sat, 12 Dec 2015 07:11:30 -0500 Subject: [PATCH 11/20] Removed deemphasis experiment and replaced with a fixed, non-clipping gain module for P25 IMBE audio. --- src/module/decode/p25/audio/P25AudioModule.java | 5 +++-- src/source/tuner/airspy/AirspyTunerController.java | 2 -- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/module/decode/p25/audio/P25AudioModule.java b/src/module/decode/p25/audio/P25AudioModule.java index c9074928..4fa08148 100644 --- a/src/module/decode/p25/audio/P25AudioModule.java +++ b/src/module/decode/p25/audio/P25AudioModule.java @@ -22,6 +22,7 @@ import audio.squelch.SquelchState; import controller.channel.ChannelEvent; import controller.channel.IChannelEventListener; import dsp.filter.iir.DeemphasisFilter; +import dsp.gain.NonClippingGain; public class P25AudioModule extends Module implements Listener, IAudioPacketProvider, IMessageListener, IChannelEventListener, @@ -42,7 +43,7 @@ public class P25AudioModule extends Module implements Listener, private AudioMetadata mAudioMetadata; private ChannelEventListener mChannelEventListener = new ChannelEventListener(); private SquelchStateListener mSquelchStateListener = new SquelchStateListener(); - private DeemphasisFilter mDeemphasis = new DeemphasisFilter( 48000.0f, 300.0f, 0.6f ); + private NonClippingGain mGain = new NonClippingGain( 5.0f, 0.95f ); public P25AudioModule( boolean record ) { @@ -109,7 +110,7 @@ public class P25AudioModule extends Module implements Listener, { float[] audio = mAudioConverter.decode( frame ); - audio = mDeemphasis.filter( audio ); + audio = mGain.apply( audio ); mAudioPacketListener.receive( new AudioPacket( audio, mAudioMetadata.copyOf() ) ); diff --git a/src/source/tuner/airspy/AirspyTunerController.java b/src/source/tuner/airspy/AirspyTunerController.java index 16fb9489..16577a55 100644 --- a/src/source/tuner/airspy/AirspyTunerController.java +++ b/src/source/tuner/airspy/AirspyTunerController.java @@ -597,8 +597,6 @@ public class AirspyTunerController extends TunerController { int count = EndianUtils.readSwappedInteger( rawCount, 0 ); - mLog.info( "There are [" + count + "] sample rates available" ); - byte[] rawRates = readArray( Command.GET_SAMPLE_RATES, 0, count, ( count * 4 ) ); From 87e70ae0e4c2bdaf493a38a70d49ee9b6450d2dd Mon Sep 17 00:00:00 2001 From: denny Date: Sat, 12 Dec 2015 11:30:39 -0500 Subject: [PATCH 12/20] Adding missing gain class --- src/dsp/gain/NonClippingGain.java | 65 +++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/dsp/gain/NonClippingGain.java diff --git a/src/dsp/gain/NonClippingGain.java b/src/dsp/gain/NonClippingGain.java new file mode 100644 index 00000000..ef1a7bcd --- /dev/null +++ b/src/dsp/gain/NonClippingGain.java @@ -0,0 +1,65 @@ +package dsp.gain; + +import sample.real.RealBuffer; + +public class NonClippingGain +{ + private float mGain; + private float mMaxValue; + + /** + * Applies a fixed gain value to the float samples. + * + * @param gain value + * @param maxValue max absolute value to prevent clipping ( 0.0 to 1.0 ) + */ + public NonClippingGain( float gain, float maxValue ) + { + mGain = gain; + mMaxValue = maxValue; + } + + /** + * Applies gain to the sample + */ + public float apply( float sample ) + { + float adjusted = sample * mGain; + + if( adjusted > mMaxValue ) + { + adjusted = mMaxValue; + } + if( adjusted < -mMaxValue ) + { + adjusted = -mMaxValue; + } + + return adjusted; + } + + /** + * Applies gain to the sample array + */ + public float[] apply( float[] samples ) + { + for( int x = 0; x < samples.length; x++ ) + { + samples[ x ] = apply( samples[ x ]); + } + + return samples; + } + + /** + * Applies gain to the real buffer + */ + public RealBuffer apply( RealBuffer buffer ) + { + float[] samples = buffer.getSamples(); + + apply( samples ); + + return buffer; + } +} From 0951277915a9d7465ef27413332ce1510b8d00e9 Mon Sep 17 00:00:00 2001 From: denny Date: Sat, 12 Dec 2015 14:08:32 -0500 Subject: [PATCH 13/20] Tuner Controller update to better handle automatic center frequency calculation when additional channels are allocated. Incorporates tuner specific definition of overall usable bandwidth, so that tuners like airspy can specify 90% of 10 MHz (ie 9 MHz) is truly usable and the fringe 10% is unusable rolloff. Also includes tuner-specific defining of the width of the central spike, or a value of 0 indicates that the entire range is usable (ie airspy). --- src/controller/channel/Channel.java | 2 +- src/source/tuner/TunerChannel.java | 20 +- src/source/tuner/TunerController.java | 246 +++++++++++++----- .../tuner/airspy/AirspyTunerController.java | 118 +-------- src/source/tuner/fcd/FCDTunerController.java | 8 +- .../tuner/hackrf/HackRFTunerController.java | 4 +- .../tuner/rtl/RTL2832TunerController.java | 9 +- .../tuner/rtl/e4k/E4KTunerController.java | 6 +- .../tuner/rtl/r820t/R820TTunerController.java | 7 +- src/spectrum/SpectralDisplayPanel.java | 1 + 10 files changed, 224 insertions(+), 197 deletions(-) diff --git a/src/controller/channel/Channel.java b/src/controller/channel/Channel.java index 0ac11a9e..3503a0e7 100644 --- a/src/controller/channel/Channel.java +++ b/src/controller/channel/Channel.java @@ -1004,7 +1004,7 @@ public class Channel extends Configuration TunerChannel tunerChannel = getTunerChannel(); return tunerChannel != null && - tunerChannel.isWithin( minimum, maximum ); + tunerChannel.overlaps( minimum, maximum ); } public void addTrafficChannel( String channelID, Channel channel ) diff --git a/src/source/tuner/TunerChannel.java b/src/source/tuner/TunerChannel.java index c873753c..7ff6e32d 100644 --- a/src/source/tuner/TunerChannel.java +++ b/src/source/tuner/TunerChannel.java @@ -1,6 +1,6 @@ /******************************************************************************* * SDR Trunk - * Copyright (C) 2014 Dennis Sheirer + * Copyright (C) 2014,2015 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -71,13 +71,17 @@ public class TunerChannel implements Comparable } /** - * Indicates if any part of this tuner channel is contained within the - * minimum and maximum frequency values. + * Indicates if any part of this tuner channel overlaps the specified range */ - public boolean isWithin( long minimum, long maximum ) + public boolean overlaps( long minimum, long maximum ) { - return ( ( minimum <= getMinFrequency() && getMinFrequency() <= maximum ) || - ( minimum <= getMaxFrequency() && getMaxFrequency() <= maximum ) ); + long channelMin = getMinFrequency(); + long channelMax = getMaxFrequency(); + + return ( ( minimum <= channelMin && channelMin <= maximum ) || + ( minimum <= channelMax && channelMax <= maximum ) || + ( minimum <= channelMin && channelMax <= maximum ) || + ( channelMin <= minimum && maximum <= channelMax ) ); } public String toString() @@ -85,9 +89,9 @@ public class TunerChannel implements Comparable StringBuilder sb = new StringBuilder(); sb.append( "Channel: " ); - sb.append( ( mFrequency - ( mBandwidth / 2 ) ) ); + sb.append( getMinFrequency() ); sb.append( "-" ); - sb.append( ( mFrequency + ( mBandwidth / 2 ) ) ); + sb.append( getMaxFrequency() ); return sb.toString(); } diff --git a/src/source/tuner/TunerController.java b/src/source/tuner/TunerController.java index 0676e4f6..1cc26637 100644 --- a/src/source/tuner/TunerController.java +++ b/src/source/tuner/TunerController.java @@ -1,6 +1,6 @@ /******************************************************************************* * SDR Trunk - * Copyright (C) 2014 Dennis Sheirer + * Copyright (C) 2014,2015 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,7 +19,6 @@ package source.tuner; import java.util.ArrayList; import java.util.Collections; -import java.util.Iterator; import java.util.concurrent.RejectedExecutionException; import org.slf4j.Logger; @@ -39,25 +38,34 @@ public abstract class TunerController implements Tunable /* List of currently tuned channels being served to demod channels */ protected ArrayList mTunedChannels = new ArrayList(); - protected FrequencyController mFrequencyController; + private int mMiddleUnusable; + private double mUsableBandwidthPercentage; /** * Abstract tuner controller class. The tuner controller manages frequency * bandwidth and currently tuned channels that are being fed samples from * the tuner. * - * @param minimumFrequency - minimum uncorrected tunable frequency - * @param maximumFrequency - maximum uncorrected tunable frequency + * @param minimumFrequency minimum uncorrected tunable frequency + * @param maximumFrequency maximum uncorrected tunable frequency + * @param middleUnusable is the +/- value center DC spike to avoid for channels + * @param extantUnusable is the unusable space at the extreme ends of the spectrum + * * @throws SourceException - for any issues related to constructing the * class, tuning a frequency, or setting the bandwidth */ - public TunerController( long minimumFrequency, long maximumFrequency ) + public TunerController( long minimumFrequency, + long maximumFrequency, + int middleUnusable, + double usableBandwidthPercentage ) { mFrequencyController = new FrequencyController( this, minimumFrequency, maximumFrequency, 0.0d ); + mMiddleUnusable = middleUnusable; + mUsableBandwidthPercentage = usableBandwidthPercentage; } /** @@ -112,24 +120,21 @@ public abstract class TunerController implements Tunable return mFrequencyController.getMaximumFrequency(); } - public long getMinTunedFrequency() + private long getMinTunedFrequency() throws SourceException { - return mFrequencyController.getFrequency() - - ( mFrequencyController.getBandwidth() / 2 ); + return mFrequencyController.getFrequency() - ( getUsableBandwidth() / 2 ); } - public long getMaxTunedFrequency() + private long getMaxTunedFrequency() throws SourceException { - return mFrequencyController.getFrequency() + - ( mFrequencyController.getBandwidth() / 2 ); + return mFrequencyController.getFrequency() + ( getUsableBandwidth() / 2 ); } /** - * Indicates if the tuner can accomodate this new channel frequency and - * bandwidth, along with all of the existing tuned channels currently in - * place. + * Indicates if channel along with all of the other currently sourced + * channels can fit within the tunable bandwidth. */ - public boolean canTuneChannel( TunerChannel channel ) + private boolean canTune( TunerChannel channel ) { //Make sure we're within the tunable frequency range of this tuner if( getMinFrequency() < channel.getMinFrequency() && @@ -145,19 +150,27 @@ public abstract class TunerController implements Tunable //Sort the existing locks and get the min/max locked frequencies Collections.sort( mTunedChannels ); + int usableBandwidth = getUsableBandwidth(); long minLockedFrequency = mTunedChannels.get( 0 ).getMinFrequency(); long maxLockedFrequency = mTunedChannels .get( mTunedChannels.size() - 1 ).getMaxFrequency(); - //Requested channel is higher than min locked frequency + //Requested channel is within current locked channel frequency range if( minLockedFrequency <= channel.getMinFrequency() && - ( channel.getMaxFrequency() - minLockedFrequency ) <= getBandwidth() ) + channel.getMaxFrequency() <= maxLockedFrequency ) + { + return true; + } + + //Requested channel is higher than min locked frequency + if( channel.getMaxFrequency() > minLockedFrequency && + channel.getMaxFrequency() - minLockedFrequency <= usableBandwidth ) { return true; } //Requested channel is lower than the max locked frequency - else if( channel.getMaxFrequency() <= maxLockedFrequency && - ( maxLockedFrequency - channel.getMinFrequency() ) <= getBandwidth() ) + if( channel.getMinFrequency() <= maxLockedFrequency && + maxLockedFrequency - channel.getMinFrequency() <= usableBandwidth ) { return true; } @@ -166,26 +179,72 @@ public abstract class TunerController implements Tunable return false; } - + + /** + * Constructs a digital drop channel (DDC) as a tuner channel from from + * the tuner, or returns null if the channel cannot be sourced from the + * tuner. + * + * This controller can't provide a channel if the channel, along with the + * current set of sourced channels, can't be accomodated within the current + * tuner bandwidth, or if a center frequency cannot be calculated that will + * ensure all of the channels can be accomodated and any defined central DC + * spike avoided. + * + * @param threadPoolManager for the channel to use for runnables + * @param tuner to source the channel from + * @param channel with defined center frequency and bandwidth + * + * @return fully constructed tuner channel or null + * + * @throws RejectedExecutionException if the decimation processor has an error + */ public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager, - Tuner tuner, TunerChannel tunerChannel ) - throws RejectedExecutionException, SourceException + Tuner tuner, TunerChannel channel ) throws RejectedExecutionException { TunerChannelSource source = null; - if( canTuneChannel( tunerChannel ) ) + if( canTune( channel ) ) { - mTunedChannels.add( tunerChannel ); - - updateLOFrequency(); - - source = new TunerChannelSource( threadPoolManager, - tuner, tunerChannel ); + try + { + mTunedChannels.add( channel ); + + if( requiresLOUpdate( channel ) ) + { + updateLOFrequency(); + } + + source = new TunerChannelSource( threadPoolManager, + tuner, channel ); + } + catch( SourceException se ) + { + mTunedChannels.remove( channel ); + source = null; + } } return source; } - + + /** + * Indicates if the tuner's LO frequency must be updated in order to + * accomodate the tuner channel + */ + private boolean requiresLOUpdate( TunerChannel channel ) throws SourceException + { + return !( getMinTunedFrequency() <= channel.getMinFrequency() && + channel.getMaxFrequency() <= getMaxTunedFrequency() && + ( ( mMiddleUnusable == 0 ) || + ( !channel.overlaps( getTunedFrequency() - mMiddleUnusable, + getTunedFrequency() + mMiddleUnusable ) ) ) ); + } + + /** + * Releases the currently sourced tuner channel from this tuner and shuts + * down the tuner if no other sources exist. + */ public void releaseChannel( TunerChannelSource tunerChannelSource ) { if( tunerChannelSource != null ) @@ -194,56 +253,115 @@ public abstract class TunerController implements Tunable } else { - mLog.error( "Tuner Controller - couldn't find the tuned channel " - + "to release it" ); + mLog.error( "Request to release channel that is not in current tuned channels set" ); } } /** - * Sets the Local Oscillator frequency to the middle of the currently - * locked frequency range, adjusting the left/right of a channel, if the - * middle falls within the locked range of any of the channels. Note: this - * will fail to set the correct frequency if multiple overlapping channel - * bandwidths are locked in the exact middle of the total locked channel - * frequency range. + * Sets the Local Oscillator frequency accomodate the current set of tuned + * channels. + * + * If there is only a single tuned channel, it is placed immediately to the + * right of the usable bandwidth right of the central DC spike. + * + * Otherwise, it places the highest channel frequency at the upper end of + * the tuner bandwidth, and then iteratively moves the center frequency + * higher until all channels fit within the bandwidth and none of the + * channels overlap any defined central DC spike unusable region. If a + * center tune frequency cannot be calculated, throw an exception so that + * the most recently added channel can be removed. + * + * Note: the tuned frequency is not changed until a legitimate new frequency + * can be calculated. If an exception is thrown, the current frequency is + * retained, so that the recently added channel can be removed and all other + * channels can continue as previously arranged. * - * @throws SourceException + * @throws SourceException if the set of tuner channels, including a recently + * added channel, cannot be tuned within the current tuner bandwidth and any + * central DC spike unusable region. */ - public void updateLOFrequency() throws SourceException + private void updateLOFrequency() throws SourceException { Collections.sort( mTunedChannels ); - long minLockedFrequency = mTunedChannels.get( 0 ).getMinFrequency(); - long maxLockedFrequency = mTunedChannels - .get( mTunedChannels.size() - 1 ).getMaxFrequency(); + long frequency = getFrequency(); + + boolean frequencyValid = true; - long middle = minLockedFrequency + - ( ( maxLockedFrequency - minLockedFrequency ) / 2 ); - long middleMin = middle - 10000; - long middleMax = middle + 10000; - - Iterator it = mTunedChannels.iterator(); - - while( it.hasNext() ) + //If there is only 1 channel, position it to the right of center + if( mTunedChannels.size() == 1 ) { - TunerChannel lock = it.next(); + frequency = mTunedChannels.get( 0 ).getMinFrequency() - mMiddleUnusable; + } + else + { + long minLockedFrequency = mTunedChannels.get( 0 ).getMinFrequency(); + long maxLockedFrequency = mTunedChannels + .get( mTunedChannels.size() - 1 ).getMaxFrequency(); - //If a locked channel overlaps our middle frequency lockout, adjust to - //the left or the right of that channel, whichever is closer - if( lock.getMinFrequency() < middleMax && middleMin < lock.getMaxFrequency() ) + //Start by placing the highest frequency channel at the high end of + //the spectrum + frequency = maxLockedFrequency - ( getUsableBandwidth() / 2 ); + + //Iterate the channels and make sure that none of them overlap the + //center DC spike buffer, if one exists + if( mMiddleUnusable > 0 ) { - if( middleMax - lock.getMinFrequency() < lock.getMaxFrequency() - middleMin ) + boolean processingRequired = true; + + while( frequencyValid && processingRequired ) { - middle = lock.getMinFrequency() - 10000; - } - else - { - middle = lock.getMaxFrequency() + 10000; + processingRequired = false; + + long minAvoid = frequency - mMiddleUnusable; + long maxAvoid = frequency + mMiddleUnusable; + + for( TunerChannel channel: mTunedChannels ) + { + if( channel.overlaps( minAvoid, maxAvoid ) ) + { + //Can we move the frequency lower so that the + //channel sits to the right of center? + long adjustment = channel.getMaxFrequency() - minAvoid; + + if( frequency + adjustment - + ( getUsableBandwidth() / 2 ) <= minLockedFrequency ) + { + frequency += adjustment; + processingRequired = true; + } + else + { + frequencyValid = false; + } + + //break out of the for/each loop, so that we can + //start over again with all of the channels + break; + } + } } } } - - mFrequencyController.setFrequency( middle ); + + if( frequencyValid ) + { + mFrequencyController.setFrequency( frequency ); + } + else + { + throw new SourceException( "Couldn't calculate viable center " + + "frequency from set of tuner channels" ); + } + } + + /** + * Usable bandwidth - total bandwidth minus the unusable space at either end + * of the spectrum. + */ + private int getUsableBandwidth() + { + return (int)( getBandwidth() * mUsableBandwidthPercentage ); } /** diff --git a/src/source/tuner/airspy/AirspyTunerController.java b/src/source/tuner/airspy/AirspyTunerController.java index 16577a55..9c8b9a1f 100644 --- a/src/source/tuner/airspy/AirspyTunerController.java +++ b/src/source/tuner/airspy/AirspyTunerController.java @@ -93,6 +93,7 @@ public class AirspyTunerController extends TunerController public static final long FREQUENCY_MIN = 24000000l; public static final long FREQUENCY_MAX = 1800000000l; public static final long FREQUENCY_DEFAULT = 101100000; + public static final double USABLE_BANDWIDTH_PERCENT = 0.90; public static final AirspySampleRate DEFAULT_SAMPLE_RATE = new AirspySampleRate( 0, 10000000, "10.00 MHz" ); @@ -136,7 +137,7 @@ public class AirspyTunerController extends TunerController public AirspyTunerController( Device device, ThreadPoolManager threadPoolManager ) throws SourceException { - super( FREQUENCY_MIN, FREQUENCY_MAX ); + super( FREQUENCY_MIN, FREQUENCY_MAX, 0, USABLE_BANDWIDTH_PERCENT ); mDevice = device; mThreadPoolManager = threadPoolManager; @@ -829,121 +830,6 @@ public class AirspyTunerController extends TunerController } } - /** - * Indicates if the tuner can accommodate this new channel frequency and - * bandwidth, along with all of the existing tuned channels currently in - * place. - */ - public boolean canTuneChannel( TunerChannel channel ) - { - //If this is the first lock, then we're good - if( mTunedChannels.isEmpty() ) - { - return true; - } - else - { - //Sort the existing locks and get the min/max locked frequencies - Collections.sort( mTunedChannels ); - - long minLockedFrequency = mTunedChannels.get( 0 ).getMinFrequency(); - long maxLockedFrequency = mTunedChannels - .get( mTunedChannels.size() - 1 ).getMaxFrequency(); - - if( channel.getMinFrequency() >= minLockedFrequency && - channel.getMaxFrequency() <= maxLockedFrequency ) - { - return true; - } - - int usable = getUsableBandwidth(); - - if( channel.getMinFrequency() < minLockedFrequency ) - { - return ( maxLockedFrequency - channel.getMinFrequency() ) <= usable; - } - - if( channel.getMaxFrequency() > maxLockedFrequency ) - { - return ( channel.getMaxFrequency() - minLockedFrequency ) <= usable; - } - } - - return false; - } - - /** - * Limits the usable bandwidth of the tuner to 90% of the bandwidth to - * account for roll-off at the band edges - */ - private int getUsableBandwidth() - { - return (int)( (double)getBandwidth() * 0.9d ); - } - - /** - * Overrides the default tuner controller method to limit the total bandwidth - * of the tuner to 90% and adjust the min tuned frequency accordingly - */ - public long getMinTunedFrequency() - { - return mFrequencyController.getFrequency() - ( getUsableBandwidth() / 2 ); - } - - /** - * Overrides the default tuner controller method to limit the total bandwidth - * of the tuner to 90% and adjust the max tuned frequency accordingly - */ - public long getMaxTunedFrequency() - { - return mFrequencyController.getFrequency() + ( getUsableBandwidth() / 2 ); - } - - public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager, - Tuner tuner, TunerChannel tunerChannel ) - throws RejectedExecutionException, SourceException - { - TunerChannelSource source = null; - - if( canTuneChannel( tunerChannel ) ) - { - mTunedChannels.add( tunerChannel ); - - Collections.sort( mTunedChannels ); - - long min = mTunedChannels.get( 0 ).getMinFrequency(); - long max = mTunedChannels.get( mTunedChannels.size() - 1 ).getMaxFrequency(); - - /* Adjust the center frequency if the set of tuner channels don't - * currently fit within the usable bandwidth and current center frequency */ - if( !( getMinTunedFrequency() <= min && max <= getMaxTunedFrequency() ) ) - { - long freq = min + ( ( max - min ) / 2 ); - - mFrequencyController.setFrequency( freq ); - } - - source = new TunerChannelSource( threadPoolManager, - tuner, tunerChannel ); - } - - return source; - } - - public void releaseChannel( TunerChannelSource tunerChannelSource ) - { - if( tunerChannelSource != null ) - { - mTunedChannels.remove( tunerChannelSource.getTunerChannel() ); - } - else - { - mLog.error( "Tuner Controller - couldn't find the tuned channel " - + "to release it" ); - } - } - - /** * Airspy Board Identifier */ diff --git a/src/source/tuner/fcd/FCDTunerController.java b/src/source/tuner/fcd/FCDTunerController.java index 03a49b56..cead5092 100644 --- a/src/source/tuner/fcd/FCDTunerController.java +++ b/src/source/tuner/fcd/FCDTunerController.java @@ -46,6 +46,9 @@ public abstract class FCDTunerController extends TunerController private final static Logger mLog = LoggerFactory.getLogger( FCDTunerController.class ); + public static final double USABLE_BANDWIDTH_PERCENT = 1.0; + public static final int DC_SPIKE_AVOID_BUFFER = 2000; + public final static byte FCD_INTERFACE = (byte)0x2; public final static byte FCD_ENDPOINT_IN = (byte)0x82; public final static byte FCD_ENDPOINT_OUT = (byte)0x2; @@ -70,7 +73,10 @@ public abstract class FCDTunerController extends TunerController int minTunableFrequency, int maxTunableFrequency ) { - super( minTunableFrequency, maxTunableFrequency ); + super( minTunableFrequency, + maxTunableFrequency, + DC_SPIKE_AVOID_BUFFER, + USABLE_BANDWIDTH_PERCENT ); mDevice = device; mDeviceDescriptor = descriptor; diff --git a/src/source/tuner/hackrf/HackRFTunerController.java b/src/source/tuner/hackrf/HackRFTunerController.java index 8d27e7f4..bdc3bce2 100644 --- a/src/source/tuner/hackrf/HackRFTunerController.java +++ b/src/source/tuner/hackrf/HackRFTunerController.java @@ -108,6 +108,8 @@ public class HackRFTunerController extends TunerController public static final long MIN_FREQUENCY = 10000000l; public static final long MAX_FREQUENCY = 6000000000l; public static final long DEFAULT_FREQUENCY = 101100000; + public static final double USABLE_BANDWIDTH_PERCENT = 0.95; + public static final int DC_SPIKE_AVOID_BUFFER = 5000; public final static int TRANSFER_BUFFER_POOL_SIZE = 16; @@ -131,7 +133,7 @@ public class HackRFTunerController extends TunerController public HackRFTunerController( Device device, DeviceDescriptor descriptor, ThreadPoolManager threadPoolManager ) throws SourceException { - super( MIN_FREQUENCY, MAX_FREQUENCY ); + super( MIN_FREQUENCY, MAX_FREQUENCY, DC_SPIKE_AVOID_BUFFER, USABLE_BANDWIDTH_PERCENT ); mDevice = device; mDeviceDescriptor = descriptor; diff --git a/src/source/tuner/rtl/RTL2832TunerController.java b/src/source/tuner/rtl/RTL2832TunerController.java index d38a8cfb..c9eb524e 100644 --- a/src/source/tuner/rtl/RTL2832TunerController.java +++ b/src/source/tuner/rtl/RTL2832TunerController.java @@ -137,9 +137,14 @@ public abstract class RTL2832TunerController extends TunerController DeviceDescriptor deviceDescriptor, ThreadPoolManager threadPoolManager, long minTunableFrequency, - long maxTunableFrequency ) throws SourceException + long maxTunableFrequency, + int centerUnusableBandwidth, + double usableBandwidthPercentage ) throws SourceException { - super( minTunableFrequency, maxTunableFrequency ); + super( minTunableFrequency, + maxTunableFrequency, + centerUnusableBandwidth, + usableBandwidthPercentage ); mThreadPoolManager = threadPoolManager; diff --git a/src/source/tuner/rtl/e4k/E4KTunerController.java b/src/source/tuner/rtl/e4k/E4KTunerController.java index 57a58578..6bdfb149 100644 --- a/src/source/tuner/rtl/e4k/E4KTunerController.java +++ b/src/source/tuner/rtl/e4k/E4KTunerController.java @@ -44,6 +44,8 @@ public class E4KTunerController extends RTL2832TunerController public static final long MIN_FREQUENCY = 52000000; public static final long MAX_FREQUENCY = 2200000000l; + public static final double USABLE_BANDWIDTH_PERCENT = 0.95; + public static final int DC_SPIKE_AVOID_BUFFER = 15000; /* The local oscillator is defined by whole (integer) units of the oscillator * frequency and fractional units representing 1/65536th of the oscillator @@ -115,8 +117,8 @@ public class E4KTunerController extends RTL2832TunerController ThreadPoolManager threadPoolManager ) throws SourceException { - super( device, deviceDescriptor, threadPoolManager, - MIN_FREQUENCY, MAX_FREQUENCY ); + super( device, deviceDescriptor, threadPoolManager, MIN_FREQUENCY, + MAX_FREQUENCY, DC_SPIKE_AVOID_BUFFER, USABLE_BANDWIDTH_PERCENT ); } @Override diff --git a/src/source/tuner/rtl/r820t/R820TTunerController.java b/src/source/tuner/rtl/r820t/R820TTunerController.java index 4d8ec9ce..607482d4 100644 --- a/src/source/tuner/rtl/r820t/R820TTunerController.java +++ b/src/source/tuner/rtl/r820t/R820TTunerController.java @@ -47,6 +47,9 @@ public class R820TTunerController extends RTL2832TunerController public static final long MIN_FREQUENCY = 3180000; public static final long MAX_FREQUENCY = 1782030000; + public static final double USABLE_BANDWIDTH_PERCENT = 1.0; + public static final int DC_SPIKE_AVOID_BUFFER = 2500; + public static final int R820T_IF_FREQUENCY = 3570000; public static final byte VERSION = (byte)49; @@ -78,8 +81,8 @@ public class R820TTunerController extends RTL2832TunerController ThreadPoolManager threadPoolManager ) throws SourceException { - super( device, deviceDescriptor, threadPoolManager, - MIN_FREQUENCY, MAX_FREQUENCY ); + super( device, deviceDescriptor, threadPoolManager, MIN_FREQUENCY, + MAX_FREQUENCY, DC_SPIKE_AVOID_BUFFER, USABLE_BANDWIDTH_PERCENT ); } @Override diff --git a/src/spectrum/SpectralDisplayPanel.java b/src/spectrum/SpectralDisplayPanel.java index 4b1252c6..dce94b2c 100644 --- a/src/spectrum/SpectralDisplayPanel.java +++ b/src/spectrum/SpectralDisplayPanel.java @@ -28,6 +28,7 @@ import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import java.text.DecimalFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Hashtable; import javax.swing.JCheckBoxMenuItem; From 9babaa3d7a094c9b65e059056d7819b0da827c20 Mon Sep 17 00:00:00 2001 From: denny Date: Sun, 13 Dec 2015 08:59:49 -0500 Subject: [PATCH 14/20] Enhances null checking in P25 decoder panel --- src/module/decode/p25/P25DecoderPanel.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/module/decode/p25/P25DecoderPanel.java b/src/module/decode/p25/P25DecoderPanel.java index ea1dd648..4dca7e7d 100644 --- a/src/module/decode/p25/P25DecoderPanel.java +++ b/src/module/decode/p25/P25DecoderPanel.java @@ -151,7 +151,7 @@ public class P25DecoderPanel extends DecoderPanel final String icon = from.getIconName(); - if( icon != null ) + if( icon != null && mSettingsManager != null ) { mFromAlias.setIcon( mSettingsManager.getImageIcon( icon, SettingsManager.DEFAULT_ICON_SIZE ) ); @@ -182,7 +182,7 @@ public class P25DecoderPanel extends DecoderPanel final String icon = to.getIconName(); - if( icon != null ) + if( icon != null && mSettingsManager != null ) { mToAlias.setIcon( mSettingsManager.getImageIcon( icon, SettingsManager.DEFAULT_ICON_SIZE ) ); From 67b4aa3a0e588b1fa830a6943279f37d5904b873 Mon Sep 17 00:00:00 2001 From: denny Date: Sun, 13 Dec 2015 09:01:29 -0500 Subject: [PATCH 15/20] Adds error handler for new audio recorder starts. Currently chasing a bug where Linux runs out of FileDescriptors, even though we're explicitly closing out file channels. Error handler sets flag to prevent any further audio wave recorders from being started and logs the same. --- src/record/RecorderManager.java | 36 ++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/record/RecorderManager.java b/src/record/RecorderManager.java index b9941926..949ac241 100644 --- a/src/record/RecorderManager.java +++ b/src/record/RecorderManager.java @@ -17,10 +17,10 @@ ******************************************************************************/ package record; - import java.io.File; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,6 +33,7 @@ import audio.AudioPacket; import audio.metadata.Metadata; import audio.metadata.MetadataType; import controller.ThreadPoolManager; +import controller.ThreadPoolManager.ThreadType; public class RecorderManager implements Listener { @@ -43,6 +44,8 @@ public class RecorderManager implements Listener private Map mRecorders = new HashMap<>(); private ThreadPoolManager mThreadPoolManager; + + private boolean mCanStartNewRecorders = true; public RecorderManager( ThreadPoolManager threadPoolManager ) { @@ -78,16 +81,29 @@ public class RecorderManager implements Listener } else if( audioPacket.getType() == AudioPacket.Type.AUDIO ) { - String filePrefix = getFilePrefix( audioPacket ); - - RealBufferWaveRecorder recorder = - new RealBufferWaveRecorder( mThreadPoolManager, - AUDIO_SAMPLE_RATE, filePrefix ); - - recorder.start(); + if( mCanStartNewRecorders ) + { + String filePrefix = getFilePrefix( audioPacket ); - recorder.receive( audioPacket.getAudioBuffer() ); - mRecorders.put( identifier, recorder ); + try + { + RealBufferWaveRecorder recorder = + new RealBufferWaveRecorder( mThreadPoolManager, + AUDIO_SAMPLE_RATE, filePrefix ); + + recorder.start(); + + recorder.receive( audioPacket.getAudioBuffer() ); + mRecorders.put( identifier, recorder ); + } + catch( Exception ioe ) + { + mCanStartNewRecorders = false; + + mLog.error( "Error attempting to start new audio wave recorder." + + " All (future) audio recording is disabled", ioe ); + } + } } } } From a42e6e81ad609c51d4a4312188d7fa2c1df725d3 Mon Sep 17 00:00:00 2001 From: denny Date: Sun, 13 Dec 2015 09:02:26 -0500 Subject: [PATCH 16/20] Adds Lineary, Sensitivity and Custom mode options to airspy tuner. --- src/source/tuner/airspy/AirspyTuner.java | 2 +- .../airspy/AirspyTunerConfiguration.java | 13 ++ .../airspy/AirspyTunerConfigurationPanel.java | 204 ++++++++++++++---- .../tuner/airspy/AirspyTunerController.java | 182 +++++++++++++++- 4 files changed, 349 insertions(+), 52 deletions(-) diff --git a/src/source/tuner/airspy/AirspyTuner.java b/src/source/tuner/airspy/AirspyTuner.java index a424d98f..36df0315 100644 --- a/src/source/tuner/airspy/AirspyTuner.java +++ b/src/source/tuner/airspy/AirspyTuner.java @@ -118,7 +118,7 @@ public class AirspyTuner extends Tuner @Override public double getSampleSize() { - return 12.0; + return 13.0; } @Override diff --git a/src/source/tuner/airspy/AirspyTunerConfiguration.java b/src/source/tuner/airspy/AirspyTunerConfiguration.java index b21c0dd6..52a3a818 100644 --- a/src/source/tuner/airspy/AirspyTunerConfiguration.java +++ b/src/source/tuner/airspy/AirspyTunerConfiguration.java @@ -21,10 +21,12 @@ import javax.xml.bind.annotation.XmlAttribute; import source.tuner.TunerConfiguration; import source.tuner.TunerType; +import source.tuner.airspy.AirspyTunerController.Gain; public class AirspyTunerConfiguration extends TunerConfiguration { private int mSampleRate = AirspyTunerController.DEFAULT_SAMPLE_RATE.getRate(); + private Gain mGain = AirspyTunerController.LINEARITY_GAIN_DEFAULT; private int mIFGain = AirspyTunerController.IF_GAIN_DEFAULT; private int mMixerGain = AirspyTunerController.MIXER_GAIN_DEFAULT; private int mLNAGain = AirspyTunerController.LNA_GAIN_DEFAULT; @@ -62,6 +64,17 @@ public class AirspyTunerConfiguration extends TunerConfiguration mSampleRate = sampleRate; } + @XmlAttribute( name = "gain" ) + public Gain getGain() + { + return mGain; + } + + public void setGain( Gain gain ) + { + mGain = gain; + } + @XmlAttribute( name = "if_gain" ) public int getIFGain() { diff --git a/src/source/tuner/airspy/AirspyTunerConfigurationPanel.java b/src/source/tuner/airspy/AirspyTunerConfigurationPanel.java index 36faa33b..e2d31475 100644 --- a/src/source/tuner/airspy/AirspyTunerConfigurationPanel.java +++ b/src/source/tuner/airspy/AirspyTunerConfigurationPanel.java @@ -49,6 +49,8 @@ import source.SourceException; import source.tuner.TunerConfiguration; import source.tuner.TunerConfigurationAssignment; import source.tuner.TunerType; +import source.tuner.airspy.AirspyTunerController.Gain; +import source.tuner.airspy.AirspyTunerController.GainMode; import controller.ResourceManager; public class AirspyTunerConfigurationPanel extends JPanel @@ -69,18 +71,28 @@ public class AirspyTunerConfigurationPanel extends JPanel private JTextField mName; private JComboBox mSampleRateCombo; - private JComboBox mDecimationCombo; - private JLabel mEffectiveRate; + private JComboBox mGainModeCombo; + private JLabel mGainLabel; + private JLabel mGainFillerLabel; + private JLabel mGainValueLabel; + private JSlider mGain; + private JLabel mIFGainLabel; + private JLabel mIFGainFillerLabel; + private JLabel mIFGainValueLabel; private JSlider mIFGain; + private JLabel mLNAGainLabel; + private JCheckBox mLNAAGC; + private JLabel mLNAGainValueLabel; private JSlider mLNAGain; + + private JLabel mMixerGainValueLabel; + private JCheckBox mMixerAGC; private JLabel mMixerGainLabel; private JSlider mMixerGain; - private JCheckBox mMixerAGC; - private JCheckBox mLNAAGC; public AirspyTunerConfigurationPanel( ResourceManager resourceManager, AirspyTunerController controller ) @@ -210,8 +222,6 @@ public class AirspyTunerConfigurationPanel extends JPanel mController.setSampleRate( rate ); mSelectedConfig.setSampleRate( rate.getRate() ); save(); - - mEffectiveRate.setText( rate.getLabel() ); } catch ( LibUsbException | UsbException e1 ) { @@ -225,31 +235,102 @@ public class AirspyTunerConfigurationPanel extends JPanel add( mSampleRateCombo, "span 3,growx" ); - /** - * Decimation - */ - add( new JLabel( "Decimation:" ) ); - mDecimationCombo = new JComboBox(); - add( mDecimationCombo, "span 3,growx" ); - mDecimationCombo.setEnabled( false ); - - /** - * Effective Rate - */ - add( new JLabel( "Effective Rate:" ) ); - mEffectiveRate = new JLabel( "10,000,000" ); - add( mEffectiveRate, "span 3,growx" ); - add( new JSeparator(), "span,growx,push" ); + /** + * Gain Mode + */ + add( new JLabel( "Gain Mode:" ) ); + mGainModeCombo = new JComboBox( GainMode.values() ); + mGainModeCombo.setSelectedItem( Gain.getGainMode( mSelectedConfig.getGain() ) ); + mGainModeCombo.addActionListener( new ActionListener() + { + @Override + public void actionPerformed( ActionEvent e ) + { + GainMode mode = (GainMode)mGainModeCombo.getSelectedItem(); + updateGainComponents( mode ); + + switch( mode ) + { + case LINEARITY: + mGain.setValue( AirspyTunerController.LINEARITY_GAIN_DEFAULT.getValue() ); + mSelectedConfig.setGain( AirspyTunerController.LINEARITY_GAIN_DEFAULT ); + break; + case SENSITIVITY: + mGain.setValue( AirspyTunerController.SENSITIVITY_GAIN_DEFAULT.getValue() ); + mSelectedConfig.setGain( AirspyTunerController.SENSITIVITY_GAIN_DEFAULT ); + break; + case CUSTOM: + default: + mLNAGain.setValue( mSelectedConfig.getLNAGain() ); + mMixerGain.setValue( mSelectedConfig.getMixerGain() ); + mIFGain.setValue( mSelectedConfig.getIFGain() ); + mSelectedConfig.setGain( Gain.CUSTOM ); + break; + } + + save(); + } + } ); + + add( mGainModeCombo, "wrap" ); + + /** + * Gain + */ + mGainLabel = new JLabel( "Gain" ); + add( mGainLabel ); + mGainFillerLabel = new JLabel( "" ); + add( mGainFillerLabel, "span 2" ); + mGainValueLabel = new JLabel( String.valueOf( mSelectedConfig.getGain().getValue() ) ); + add( mGainValueLabel, "wrap" ); + mGain = new JSlider( JSlider.HORIZONTAL, + AirspyTunerController.GAIN_MIN, + AirspyTunerController.GAIN_MAX, + mSelectedConfig.getGain().getValue() ); + mGain.setMajorTickSpacing( 1 ); + mGain.setPaintTicks( true ); + + mGain.addChangeListener( new ChangeListener() + { + @Override + public void stateChanged( ChangeEvent event ) + { + GainMode mode = (GainMode)mGainModeCombo.getSelectedItem(); + int value = mGain.getValue(); + Gain gain = Gain.getGain( mode, value ); + + try + { + mController.setGain( gain ); + mSelectedConfig.setGain( gain ); + save(); + mGainValueLabel.setText( String.valueOf( gain.getValue() ) ); + } + catch ( Exception e ) + { + mLog.error( "Couldn't set airspy gain to:" + gain.name(), e ); + + JOptionPane.showMessageDialog( mGain, + "Couldn't set gain value to " + gain.getValue() ); + + mGain.setValue( mSelectedConfig.getGain().getValue() ); + } + } + } ); + + add( mGain, "span,grow" ); + /** * IF/VGA Gain */ - add( new JLabel( "IF Gain" ) ); - add( new JLabel( "" ), "span 2" ); - - mIFGainLabel = new JLabel( String.valueOf( mSelectedConfig.getIFGain() ) ); - add( mIFGainLabel, "wrap" ); + mIFGainLabel = new JLabel( "IF Gain" ); + add( mIFGainLabel ); + mIFGainFillerLabel = new JLabel( "" ); + add( mIFGainFillerLabel, "span 2" ); + mIFGainValueLabel = new JLabel( String.valueOf( mSelectedConfig.getIFGain() ) ); + add( mIFGainValueLabel, "wrap" ); mIFGain = new JSlider( JSlider.HORIZONTAL, AirspyTunerController.IF_GAIN_MIN, @@ -271,7 +352,7 @@ public class AirspyTunerConfigurationPanel extends JPanel mController.setIFGain( gain ); mSelectedConfig.setIFGain( gain ); save(); - mIFGainLabel.setText( String.valueOf( gain ) ); + mIFGainValueLabel.setText( String.valueOf( gain ) ); } catch ( Exception e ) { @@ -287,12 +368,14 @@ public class AirspyTunerConfigurationPanel extends JPanel add( mIFGain, "span,grow" ); - add( new JLabel( "Mixer Gain" ) ); + /** + * Mixer Gain + */ + mMixerGainLabel = new JLabel( "Mixer Gain" ); + add( mMixerGainLabel ); mMixerAGC = new JCheckBox( "AGC" ); - mMixerAGC.setSelected( mSelectedConfig.isMixerAGC() ); - mMixerAGC.addActionListener( new ActionListener() { @Override @@ -315,8 +398,8 @@ public class AirspyTunerConfigurationPanel extends JPanel add( mMixerAGC, "span 2,center" ); - mMixerGainLabel = new JLabel( String.valueOf( mSelectedConfig.getMixerGain() ) ); - add( mMixerGainLabel, "wrap" ); + mMixerGainValueLabel = new JLabel( String.valueOf( mSelectedConfig.getMixerGain() ) ); + add( mMixerGainValueLabel, "wrap" ); mMixerGain = new JSlider( JSlider.HORIZONTAL, AirspyTunerController.MIXER_GAIN_MIN, @@ -338,7 +421,7 @@ public class AirspyTunerConfigurationPanel extends JPanel mController.setMixerGain( gain ); mSelectedConfig.setMixerGain( gain ); save(); - mMixerGainLabel.setText( String.valueOf( gain ) ); + mMixerGainValueLabel.setText( String.valueOf( gain ) ); } catch ( Exception e ) { @@ -352,13 +435,15 @@ public class AirspyTunerConfigurationPanel extends JPanel } ); add( mMixerGain, "span,grow" ); - - add( new JLabel( "LNA Gain" ) ); + + /** + * LNA Gain + */ + mLNAGainLabel = new JLabel( "LNA Gain" ); + add( mLNAGainLabel ); mLNAAGC = new JCheckBox( "AGC" ); - mLNAAGC.setSelected( mSelectedConfig.isLNAAGC() ); - mLNAAGC.addActionListener( new ActionListener() { @Override @@ -381,8 +466,8 @@ public class AirspyTunerConfigurationPanel extends JPanel add( mLNAAGC, "span 2,center" ); - mLNAGainLabel = new JLabel( String.valueOf( mSelectedConfig.getLNAGain() ) ); - add( mLNAGainLabel, "wrap" ); + mLNAGainValueLabel = new JLabel( String.valueOf( mSelectedConfig.getLNAGain() ) ); + add( mLNAGainValueLabel, "wrap" ); mLNAGain = new JSlider( JSlider.HORIZONTAL, AirspyTunerController.LNA_GAIN_MIN, @@ -404,7 +489,7 @@ public class AirspyTunerConfigurationPanel extends JPanel mController.setLNAGain( gain ); mSelectedConfig.setLNAGain( gain ); save(); - mLNAGainLabel.setText( String.valueOf( gain ) ); + mLNAGainValueLabel.setText( String.valueOf( gain ) ); } catch ( Exception e ) { @@ -418,6 +503,9 @@ public class AirspyTunerConfigurationPanel extends JPanel } ); add( mLNAGain, "span,grow" ); + + //Set the enabled state of each of the gain controls + updateGainComponents( (GainMode)mGainModeCombo.getSelectedItem() ); /** * Create a new configuration @@ -485,6 +573,34 @@ public class AirspyTunerConfigurationPanel extends JPanel add( mDeleteConfiguration, "span 2,growx,push" ); } + + /** + * Updates the enabled state of each of the gain controls according to the + * specified gain mode. The overall gain controls are enabled for linearity + * and sensitivity and the individual gain controls are disabled, and + * vice-versa for custom mode. + */ + private void updateGainComponents( GainMode mode ) + { + boolean isCustom = ( mode == GainMode.CUSTOM ); + + mGainLabel.setEnabled( !isCustom ); + mGainFillerLabel.setEnabled( !isCustom ); + mGainValueLabel.setEnabled( !isCustom ); + mGain.setEnabled( !isCustom ); + mIFGainLabel.setEnabled( isCustom ); + mIFGainFillerLabel.setEnabled( isCustom ); + mIFGainValueLabel.setEnabled( isCustom ); + mIFGain.setEnabled( isCustom ); + mLNAGainLabel.setEnabled( isCustom ); + mLNAAGC.setEnabled( isCustom ); + mLNAGainValueLabel.setEnabled( isCustom ); + mLNAGain.setEnabled( isCustom ); + mMixerGainValueLabel.setEnabled( isCustom ); + mMixerAGC.setEnabled( isCustom ); + mMixerGainLabel.setEnabled( isCustom ); + mMixerGain.setEnabled( isCustom ); + } /** * Updates gui controls with the values from the tuner configuration @@ -507,9 +623,7 @@ public class AirspyTunerConfigurationPanel extends JPanel { mSampleRateCombo.setSelectedItem( rate ); } - - //TODO: set decimation and effective rate here - + mIFGain.setValue( mSelectedConfig.getIFGain() ); mMixerGain.setValue( mSelectedConfig.getMixerGain() ); mLNAGain.setValue( mSelectedConfig.getLNAGain() ); @@ -517,6 +631,10 @@ public class AirspyTunerConfigurationPanel extends JPanel mMixerAGC.setSelected( mSelectedConfig.isMixerAGC() ); mLNAAGC.setSelected( mSelectedConfig.isLNAAGC() ); + Gain gain = mSelectedConfig.getGain(); + mGainModeCombo.setSelectedItem( Gain.getGainMode( gain ) ); + mGain.setValue( gain.getValue() ); + mResourceManager.getSettingsManager().setSelectedTunerConfiguration( TunerType.AIRSPY_R820T, mController.getDeviceInfo().getSerialNumber(), config ); diff --git a/src/source/tuner/airspy/AirspyTunerController.java b/src/source/tuner/airspy/AirspyTunerController.java index 9c8b9a1f..755465d0 100644 --- a/src/source/tuner/airspy/AirspyTunerController.java +++ b/src/source/tuner/airspy/AirspyTunerController.java @@ -4,11 +4,10 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.text.DecimalFormat; import java.util.ArrayList; -import java.util.Collections; +import java.util.EnumSet; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.LinkedTransferQueue; -import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -29,9 +28,6 @@ import sample.Broadcaster; import sample.Listener; import sample.complex.ComplexBuffer; import source.SourceException; -import source.tuner.Tuner; -import source.tuner.TunerChannel; -import source.tuner.TunerChannelSource; import source.tuner.TunerConfiguration; import source.tuner.TunerController; import controller.ThreadPoolManager; @@ -81,15 +77,19 @@ import dsp.filter.hilbert.HilbertTransform; public class AirspyTunerController extends TunerController { + public static final Gain LINEARITY_GAIN_DEFAULT = Gain.LINEARITY_14; + public static final Gain SENSITIVITY_GAIN_DEFAULT = Gain.SENSITIVITY_10; + public static final int GAIN_MIN = 1; + public static final int GAIN_MAX = 22; public static final int LNA_GAIN_MIN = 0; public static final int LNA_GAIN_MAX = 14; - public static final int LNA_GAIN_DEFAULT = 1; + public static final int LNA_GAIN_DEFAULT = 7; public static final int MIXER_GAIN_MIN = 0; public static final int MIXER_GAIN_MAX = 15; - public static final int MIXER_GAIN_DEFAULT = 5; + public static final int MIXER_GAIN_DEFAULT = 9; public static final int IF_GAIN_MIN = 0; public static final int IF_GAIN_MAX = 15; - public static final int IF_GAIN_DEFAULT = 5; + public static final int IF_GAIN_DEFAULT = 9; public static final long FREQUENCY_MIN = 24000000l; public static final long FREQUENCY_MAX = 1800000000l; public static final long FREQUENCY_DEFAULT = 101100000; @@ -296,6 +296,11 @@ public class AirspyTunerController extends TunerController setMixerAGC( airspy.isMixerAGC() ); setLNAAGC( airspy.isLNAAGC() ); + + //Set the gain mode last, so custom values are already set, and + //linearity and sensitivity modes will automatically override + //the custom values. + setGain( airspy.getGain() ); } catch( Exception e ) { @@ -482,6 +487,18 @@ public class AirspyTunerController extends TunerController throw new UsbException( "Couldnt set LNA AGC enabled: " + enabled ); } } + + public void setGain( Gain gain ) throws UsbException + { + if( gain != Gain.CUSTOM ) + { + setMixerAGC( false ); + setLNAAGC( false ); + setLNAGain( gain.getLNA() ); + setMixerGain( gain.getMixer() ); + setIFGain( gain.getIF() ); + } + } /** * Sets LNA gain @@ -829,6 +846,155 @@ public class AirspyTunerController extends TunerController LibUsb.ERROR_NO_DEVICE ); } } + + public enum GainMode + { + LINEARITY, + SENSITIVITY, + CUSTOM; + } + + public enum Gain + { + LINEARITY_1( 1, 4, 0, 0 ), + LINEARITY_2( 2, 5, 0, 0 ), + LINEARITY_3( 3, 6, 1, 0 ), + LINEARITY_4( 4, 7, 1, 0 ), + LINEARITY_5( 5, 8, 1, 0 ), + LINEARITY_6( 6, 9, 1, 0 ), + LINEARITY_7( 7, 10, 2, 0 ), + LINEARITY_8( 8, 10, 2, 1 ), + LINEARITY_9( 9, 10, 0, 3 ), + LINEARITY_10( 10, 10, 0, 5 ), + LINEARITY_11( 11, 10, 1, 6 ), + LINEARITY_12( 12, 10, 0, 8 ), + LINEARITY_13( 13, 10, 0, 9 ), + LINEARITY_14( 14, 10, 5, 8 ), + LINEARITY_15( 15, 10, 6, 9 ), + LINEARITY_16( 16, 11, 6, 9 ), + LINEARITY_17( 17, 11, 7, 10 ), + LINEARITY_18( 18, 11, 8, 12 ), + LINEARITY_19( 19, 11, 9, 13 ), + LINEARITY_20( 20, 11, 11, 14 ), + LINEARITY_21( 21, 12, 12, 14 ), + LINEARITY_22( 22, 13, 12, 14 ), + SENSITIVITY_1( 1, 4, 0, 0 ), + SENSITIVITY_2( 2, 4, 0, 1 ), + SENSITIVITY_3( 3, 4, 0, 2 ), + SENSITIVITY_4( 4, 4, 0, 3 ), + SENSITIVITY_5( 5, 4, 1, 5 ), + SENSITIVITY_6( 6, 4, 2, 6 ), + SENSITIVITY_7( 7, 4, 2, 7 ), + SENSITIVITY_8( 8, 4, 3, 8 ), + SENSITIVITY_9( 9, 4, 4, 9 ), + SENSITIVITY_10( 10, 5, 4, 9 ), + SENSITIVITY_11( 11, 5, 4, 12 ), + SENSITIVITY_12( 12, 5, 7, 12 ), + SENSITIVITY_13( 13, 5, 8, 13 ), + SENSITIVITY_14( 14, 5, 9, 14 ), + SENSITIVITY_15( 15, 6, 9, 14 ), + SENSITIVITY_16( 16, 7, 10, 14 ), + SENSITIVITY_17( 17, 8, 10, 14 ), + SENSITIVITY_18( 18, 9, 11, 14 ), + SENSITIVITY_19( 19, 10, 12, 14 ), + SENSITIVITY_20( 20, 11, 12, 14 ), + SENSITIVITY_21( 21, 12, 12, 14 ), + SENSITIVITY_22( 22, 13, 12, 14 ), + CUSTOM( 1, 0, 0, 0 ); + + private int mValue; + private int mIF; + private int mMixer; + private int mLNA; + + private Gain( int value, int ifGain, int mixer, int lna ) + { + mValue = value; + mIF = ifGain; + mMixer = mixer; + mLNA = lna; + } + + public int getValue() + { + return mValue; + } + + public int getIF() + { + return mIF; + } + + public int getMixer() + { + return mMixer; + } + + public int getLNA() + { + return mLNA; + } + + public static Gain getGain( GainMode mode, int value ) + { + assert( GAIN_MIN <= value && value <= GAIN_MAX ); + + switch( mode ) + { + case LINEARITY: + for( Gain gain: getLinearityGains() ) + { + if( gain.getValue() == value ) + { + return gain; + } + } + return LINEARITY_GAIN_DEFAULT; + case SENSITIVITY: + for( Gain gain: getSensitivityGains() ) + { + if( gain.getValue() == value ) + { + return gain; + } + } + return SENSITIVITY_GAIN_DEFAULT; + case CUSTOM: + default: + return Gain.CUSTOM; + } + } + + public static GainMode getGainMode( Gain gain ) + { + if( gain == CUSTOM ) + { + return GainMode.CUSTOM; + } + else if( getLinearityGains().contains( gain ) ) + { + return GainMode.LINEARITY; + } + else if( getSensitivityGains().contains( gain ) ) + { + return GainMode.SENSITIVITY; + } + + return GainMode.CUSTOM; + } + + public static EnumSet getLinearityGains() + { + return EnumSet.range( LINEARITY_1, LINEARITY_22 ); + } + + public static EnumSet getSensitivityGains() + { + return EnumSet.range( SENSITIVITY_1, SENSITIVITY_22 ); + } + } + + /** * Airspy Board Identifier From 9c436033fb91f31b699e80b60524d3fa0ed20385 Mon Sep 17 00:00:00 2001 From: denny Date: Sun, 13 Dec 2015 09:22:47 -0500 Subject: [PATCH 17/20] Resolves issue with audio playback where a call starts but not enough audio samples are produced to cause the audio channel to start playback (doesn't exceed threshold), causing the audio channel to become locked until there was new audio from the same talkgroup. --- src/audio/AudioManager.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/audio/AudioManager.java b/src/audio/AudioManager.java index 39d1834a..239ebf24 100644 --- a/src/audio/AudioManager.java +++ b/src/audio/AudioManager.java @@ -275,7 +275,7 @@ public class AudioManager implements Listener, IAudioController private AudioOutput mAudioOutput; private int mPriority; - private long mAudioStoppedTime = Long.MAX_VALUE; + private long mAudioStoppedTime = System.currentTimeMillis(); public AudioChannelAssignment( AudioOutput audioOutput, int priority ) { @@ -318,8 +318,6 @@ public class AudioManager implements Listener, IAudioController switch( event.getType() ) { case AUDIO_STARTED: - mAudioStoppedTime = Long.MAX_VALUE; - break; case AUDIO_STOPPED: mAudioStoppedTime = System.currentTimeMillis(); break; From 2bcffa0f44f1502b8686badf2df3af2e1294650a Mon Sep 17 00:00:00 2001 From: denny Date: Sun, 13 Dec 2015 09:37:25 -0500 Subject: [PATCH 18/20] Resolves issue with persistent audio channel threads when changing audio output mixers. --- src/audio/AudioManager.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/audio/AudioManager.java b/src/audio/AudioManager.java index 239ebf24..cb878e78 100644 --- a/src/audio/AudioManager.java +++ b/src/audio/AudioManager.java @@ -343,8 +343,18 @@ public class AudioManager implements Listener, IAudioController endAllAudioAssignments(); + for( Entry e: mChannelAssignments.entrySet() ) + { + e.getValue().getAudioOutput().dispose(); + } mChannelAssignments.clear(); + + for( AudioOutput output: mAvailableAudioOutputs ) + { + output.dispose(); + } mAvailableAudioOutputs.clear(); + mChannelNames.clear(); mAudioOutputMap.clear(); From 47f36590fb143e10bddce9cdc5690a6f3f6a6aac Mon Sep 17 00:00:00 2001 From: denny Date: Sun, 13 Dec 2015 09:45:52 -0500 Subject: [PATCH 19/20] Resolved issue with audio cycling between channels every 2 seconds when audio mixer has stereo channel output. --- src/audio/AudioEvent.java | 1 + src/audio/AudioManager.java | 1 + src/audio/output/MonoAudioOutput.java | 5 +++++ src/audio/output/StereoAudioOutput.java | 5 +++++ 4 files changed, 12 insertions(+) diff --git a/src/audio/AudioEvent.java b/src/audio/AudioEvent.java index 146d31d2..ca166059 100644 --- a/src/audio/AudioEvent.java +++ b/src/audio/AudioEvent.java @@ -29,6 +29,7 @@ public class AudioEvent AUDIO_UNSQUELCHED, AUDIO_STARTED, AUDIO_STOPPED, + AUDIO_CONTINUATION, AUDIO_CONFIGURATION_CHANGE_STARTED, AUDIO_CONFIGURATION_CHANGE_COMPLETE; } diff --git a/src/audio/AudioManager.java b/src/audio/AudioManager.java index cb878e78..90bb681e 100644 --- a/src/audio/AudioManager.java +++ b/src/audio/AudioManager.java @@ -319,6 +319,7 @@ public class AudioManager implements Listener, IAudioController { case AUDIO_STARTED: case AUDIO_STOPPED: + case AUDIO_CONTINUATION: mAudioStoppedTime = System.currentTimeMillis(); break; default: diff --git a/src/audio/output/MonoAudioOutput.java b/src/audio/output/MonoAudioOutput.java index 5715fa78..0f4d2674 100644 --- a/src/audio/output/MonoAudioOutput.java +++ b/src/audio/output/MonoAudioOutput.java @@ -51,6 +51,7 @@ public class MonoAudioOutput extends AudioOutput private AudioEvent mAudioStartEvent; private AudioEvent mAudioStopEvent; + private AudioEvent mAudioContinuationEvent; public MonoAudioOutput( Mixer mixer ) { @@ -60,6 +61,8 @@ public class MonoAudioOutput extends AudioOutput getChannelName() ); mAudioStopEvent = new AudioEvent( AudioEvent.Type.AUDIO_STOPPED, getChannelName() ); + mAudioContinuationEvent = new AudioEvent( AudioEvent.Type.AUDIO_CONTINUATION, + getChannelName() ); try { @@ -130,6 +133,8 @@ public class MonoAudioOutput extends AudioOutput if( packets.size() > 0 ) { + broadcast( mAudioContinuationEvent ); + for( AudioPacket packet: packets ) { if( mCanProcessAudio ) diff --git a/src/audio/output/StereoAudioOutput.java b/src/audio/output/StereoAudioOutput.java index a8182359..db54e80f 100644 --- a/src/audio/output/StereoAudioOutput.java +++ b/src/audio/output/StereoAudioOutput.java @@ -51,6 +51,7 @@ public class StereoAudioOutput extends AudioOutput private AudioEvent mAudioStartEvent; private AudioEvent mAudioStopEvent; + private AudioEvent mAudioContinuationEvent; private MixerChannel mMixerChannel; @@ -66,6 +67,8 @@ public class StereoAudioOutput extends AudioOutput getChannelName() ); mAudioStopEvent = new AudioEvent( AudioEvent.Type.AUDIO_STOPPED, getChannelName() ); + mAudioContinuationEvent = new AudioEvent( AudioEvent.Type.AUDIO_CONTINUATION, + getChannelName() ); try { @@ -166,6 +169,8 @@ public class StereoAudioOutput extends AudioOutput if( packets.size() > 0 ) { + broadcast( mAudioContinuationEvent ); + for( AudioPacket packet: packets ) { if( mCanProcessAudio ) From c89a68d9fc09d862ce1e0151de1bffb373319e20 Mon Sep 17 00:00:00 2001 From: denny Date: Sun, 13 Dec 2015 10:14:48 -0500 Subject: [PATCH 20/20] Updates to jmbe library 0.3.2 --- .classpath | 2 +- ...ace-0.3.1.jar => jmbe-interface-0.3.2.jar} | Bin 16374 -> 16373 bytes 2 files changed, 1 insertion(+), 1 deletion(-) rename imports/{jmbe-interface-0.3.1.jar => jmbe-interface-0.3.2.jar} (93%) diff --git a/.classpath b/.classpath index e69bc147..a630c18d 100644 --- a/.classpath +++ b/.classpath @@ -24,6 +24,6 @@ - + diff --git a/imports/jmbe-interface-0.3.1.jar b/imports/jmbe-interface-0.3.2.jar similarity index 93% rename from imports/jmbe-interface-0.3.1.jar rename to imports/jmbe-interface-0.3.2.jar index 5b63b64065b7fd9702dcb4bf80f72378ba8631a6..29100a3fe0449724ef9dc19ad2f736d52a9bd289 100644 GIT binary patch delta 497 zcmexX|Fxbsz?+$ci-CcIfk7v*cOtI_r*>ek`?A30sVgSh>DOyX_=qPj)({hnzNsQz zyr?{Jv*4dcBsL^0O7Uq z^XsxEoi^%SmBKbzIk{F{!{+9r*v!Ph5MvAC0RYobnbH6N delta 520 zcmexb|E-=kz?+$ci-CcIf#JPX{zP64&UaS%?#B8#Q7b3f>DLDa_=s-}6zT3*n=|R^ z9F_9K&0IgL{aH57HUIpqGo!f o*C)@jH&G5YV_*n!b#(D{)yqu@@MdKL*~JBf%}fjov9=%{0Qo(mEdT%j