diff --git a/.classpath b/.classpath index e69bc147..a630c18d 100644 --- a/.classpath +++ b/.classpath @@ -24,6 +24,6 @@ - + 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 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 5b63b640..29100a3f 100644 Binary files a/imports/jmbe-interface-0.3.1.jar and b/imports/jmbe-interface-0.3.2.jar differ 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 39d1834a..90bb681e 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,9 +318,8 @@ public class AudioManager implements Listener, IAudioController switch( event.getType() ) { case AUDIO_STARTED: - mAudioStoppedTime = Long.MAX_VALUE; - break; case AUDIO_STOPPED: + case AUDIO_CONTINUATION: mAudioStoppedTime = System.currentTimeMillis(); break; default: @@ -345,8 +344,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(); 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 2a7b9dad..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 { @@ -95,7 +98,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() + "]" ); } } @@ -165,6 +169,8 @@ public class StereoAudioOutput extends AudioOutput if( packets.size() > 0 ) { + broadcast( mAudioContinuationEvent ); + for( AudioPacket packet: packets ) { if( mCanProcessAudio ) 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/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/controller/channel/Channel.java b/src/controller/channel/Channel.java index 234ecdfe..3503a0e7 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 } } } @@ -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/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/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/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; + } +} 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 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/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/P25DecoderPanel.java b/src/module/decode/p25/P25DecoderPanel.java index 15520e0b..4dca7e7d 100644 --- a/src/module/decode/p25/P25DecoderPanel.java +++ b/src/module/decode/p25/P25DecoderPanel.java @@ -149,9 +149,9 @@ public class P25DecoderPanel extends DecoderPanel { mFromAlias.setText( from.getName() ); - String icon = from.getIconName(); + final String icon = from.getIconName(); - if( icon != null ) + if( icon != null && mSettingsManager != null ) { mFromAlias.setIcon( mSettingsManager.getImageIcon( icon, SettingsManager.DEFAULT_ICON_SIZE ) ); @@ -180,9 +180,9 @@ public class P25DecoderPanel extends DecoderPanel { mToAlias.setText( to.getName() ); - String icon = to.getIconName(); + final String icon = to.getIconName(); - if( icon != null ) + if( icon != null && mSettingsManager != null ) { mToAlias.setIcon( mSettingsManager.getImageIcon( icon, SettingsManager.DEFAULT_ICON_SIZE ) ); 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 */ diff --git a/src/module/decode/p25/audio/P25AudioModule.java b/src/module/decode/p25/audio/P25AudioModule.java index 3d3f0d9b..4fa08148 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.filter.iir.DeemphasisFilter; +import dsp.gain.NonClippingGain; public class P25AudioModule extends Module implements Listener, IAudioPacketProvider, IMessageListener, IChannelEventListener, @@ -43,6 +43,7 @@ public class P25AudioModule extends Module implements Listener, private AudioMetadata mAudioMetadata; private ChannelEventListener mChannelEventListener = new ChannelEventListener(); private SquelchStateListener mSquelchStateListener = new SquelchStateListener(); + private NonClippingGain mGain = new NonClippingGain( 5.0f, 0.95f ); public P25AudioModule( boolean record ) { @@ -89,9 +90,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 +103,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 = mGain.apply( audio ); + mAudioPacketListener.receive( new AudioPacket( audio, mAudioMetadata.copyOf() ) ); } 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 ); 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/record/RecorderManager.java b/src/record/RecorderManager.java index e247036b..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 ) { @@ -72,22 +75,35 @@ 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 ) { - 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 ); + } + } } } } 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 { 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/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(); } } } diff --git a/src/source/tuner/TunerController.java b/src/source/tuner/TunerController.java index 7b1aaea3..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/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 97d4098b..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,18 +77,23 @@ 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; + 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; @@ -295,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 ) { @@ -481,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 @@ -597,8 +615,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 ) ); @@ -830,90 +846,154 @@ public class AirspyTunerController extends TunerController LibUsb.ERROR_NO_DEVICE ); } } - - /** - * Indicates if the tuner can accomodate 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; - } + 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 ); - if( channel.getMinFrequency() < minLockedFrequency ) - { - return ( maxLockedFrequency - channel.getMinFrequency() ) < getBandwidth() - 1E5; - } + 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 ); - if( channel.getMaxFrequency() > maxLockedFrequency ) + switch( mode ) { - return ( channel.getMaxFrequency() - minLockedFrequency ) <= getBandwidth() - 1E5; + 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; } } - return false; + 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 ); + } } - 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(); - - if( !( getMinTunedFrequency() <= min && - max <= getMaxTunedFrequency() ) ) - { - long freq = min + ( mFrequencyController.getBandwidth() / 2 ) + 50000; - - 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" ); - } - } /** 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/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 ); } 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/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; 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,