This commit is contained in:
Denny Sheirer
2015-12-16 00:11:58 +00:00
42 changed files with 882 additions and 355 deletions

View File

@@ -24,6 +24,6 @@
<classpathentry kind="lib" path="imports/logback-classic-1.1.2.jar"/> <classpathentry kind="lib" path="imports/logback-classic-1.1.2.jar"/>
<classpathentry kind="lib" path="imports/logback-core-1.1.2.jar"/> <classpathentry kind="lib" path="imports/logback-core-1.1.2.jar"/>
<classpathentry kind="lib" path="imports/commons-io-2.4.jar"/> <classpathentry kind="lib" path="imports/commons-io-2.4.jar"/>
<classpathentry kind="lib" path="imports/jmbe-interface-0.3.1.jar"/> <classpathentry kind="lib" path="imports/jmbe-interface-0.3.2.jar"/>
<classpathentry kind="output" path="bin"/> <classpathentry kind="output" path="bin"/>
</classpath> </classpath>

View File

@@ -1,15 +1,22 @@
0.2.0 2015xxxx Major overhaul of underlying processing chain and extensive 0.2.0 2015xxxx
updates to all decoders and filters - Major overhaul of underlying processing chain and extensive updates to all
- Resolved issue with memory leaks and crashes decoders and filters
- New audio playback interface with global mute and support for one or two - New audio playback interface with global mute and support for one or two
audio channels audio channels
- Audio playback is prioritized using a new Call Priority alias action - Audio playback is prioritized using a new Call Priority alias action
with selected channels having the highest priority and any audio without with selected channels having the highest priority and any audio without
an alias identifier having the lowest priority. an alias identifier having the lowest priority.
- Audio recording by call with new 'Non-Recordable' alias action - Updated Audio recording per call with new 'Non-Recordable' alias action
- (planned)Added Ettus UHD support for B100 with WBX tuner - Updated Baseband (I/Q) recording at 48 kHz
- Added Airspy tuner support - 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 0.1.5 20150612 Added initial P25 Decoder support with integrated audio decoding
using the JMBE library using the JMBE library

View File

@@ -29,6 +29,7 @@ public class AudioEvent
AUDIO_UNSQUELCHED, AUDIO_UNSQUELCHED,
AUDIO_STARTED, AUDIO_STARTED,
AUDIO_STOPPED, AUDIO_STOPPED,
AUDIO_CONTINUATION,
AUDIO_CONFIGURATION_CHANGE_STARTED, AUDIO_CONFIGURATION_CHANGE_STARTED,
AUDIO_CONFIGURATION_CHANGE_COMPLETE; AUDIO_CONFIGURATION_CHANGE_COMPLETE;
} }

View File

@@ -275,7 +275,7 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
private AudioOutput mAudioOutput; private AudioOutput mAudioOutput;
private int mPriority; private int mPriority;
private long mAudioStoppedTime = Long.MAX_VALUE; private long mAudioStoppedTime = System.currentTimeMillis();
public AudioChannelAssignment( AudioOutput audioOutput, int priority ) public AudioChannelAssignment( AudioOutput audioOutput, int priority )
{ {
@@ -318,9 +318,8 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
switch( event.getType() ) switch( event.getType() )
{ {
case AUDIO_STARTED: case AUDIO_STARTED:
mAudioStoppedTime = Long.MAX_VALUE;
break;
case AUDIO_STOPPED: case AUDIO_STOPPED:
case AUDIO_CONTINUATION:
mAudioStoppedTime = System.currentTimeMillis(); mAudioStoppedTime = System.currentTimeMillis();
break; break;
default: default:
@@ -345,8 +344,18 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
endAllAudioAssignments(); endAllAudioAssignments();
for( Entry<Integer,AudioChannelAssignment> e: mChannelAssignments.entrySet() )
{
e.getValue().getAudioOutput().dispose();
}
mChannelAssignments.clear(); mChannelAssignments.clear();
for( AudioOutput output: mAvailableAudioOutputs )
{
output.dispose();
}
mAvailableAudioOutputs.clear(); mAvailableAudioOutputs.clear();
mChannelNames.clear(); mChannelNames.clear();
mAudioOutputMap.clear(); mAudioOutputMap.clear();

View File

@@ -51,6 +51,7 @@ public class MonoAudioOutput extends AudioOutput
private AudioEvent mAudioStartEvent; private AudioEvent mAudioStartEvent;
private AudioEvent mAudioStopEvent; private AudioEvent mAudioStopEvent;
private AudioEvent mAudioContinuationEvent;
public MonoAudioOutput( Mixer mixer ) public MonoAudioOutput( Mixer mixer )
{ {
@@ -60,6 +61,8 @@ public class MonoAudioOutput extends AudioOutput
getChannelName() ); getChannelName() );
mAudioStopEvent = new AudioEvent( AudioEvent.Type.AUDIO_STOPPED, mAudioStopEvent = new AudioEvent( AudioEvent.Type.AUDIO_STOPPED,
getChannelName() ); getChannelName() );
mAudioContinuationEvent = new AudioEvent( AudioEvent.Type.AUDIO_CONTINUATION,
getChannelName() );
try try
{ {
@@ -130,6 +133,8 @@ public class MonoAudioOutput extends AudioOutput
if( packets.size() > 0 ) if( packets.size() > 0 )
{ {
broadcast( mAudioContinuationEvent );
for( AudioPacket packet: packets ) for( AudioPacket packet: packets )
{ {
if( mCanProcessAudio ) if( mCanProcessAudio )

View File

@@ -51,6 +51,7 @@ public class StereoAudioOutput extends AudioOutput
private AudioEvent mAudioStartEvent; private AudioEvent mAudioStartEvent;
private AudioEvent mAudioStopEvent; private AudioEvent mAudioStopEvent;
private AudioEvent mAudioContinuationEvent;
private MixerChannel mMixerChannel; private MixerChannel mMixerChannel;
@@ -66,6 +67,8 @@ public class StereoAudioOutput extends AudioOutput
getChannelName() ); getChannelName() );
mAudioStopEvent = new AudioEvent( AudioEvent.Type.AUDIO_STOPPED, mAudioStopEvent = new AudioEvent( AudioEvent.Type.AUDIO_STOPPED,
getChannelName() ); getChannelName() );
mAudioContinuationEvent = new AudioEvent( AudioEvent.Type.AUDIO_CONTINUATION,
getChannelName() );
try try
{ {
@@ -95,7 +98,8 @@ public class StereoAudioOutput extends AudioOutput
catch ( LineUnavailableException e ) catch ( LineUnavailableException e )
{ {
mLog.error( "Couldn't obtain source data line for 48kHz PCM stereo " 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 ) if( packets.size() > 0 )
{ {
broadcast( mAudioContinuationEvent );
for( AudioPacket packet: packets ) for( AudioPacket packet: packets )
{ {
if( mCanProcessAudio ) if( mCanProcessAudio )

View File

@@ -40,6 +40,11 @@ public class FloatCircularBuffer
Arrays.fill( mBuffer, (float)0.0 ); Arrays.fill( mBuffer, (float)0.0 );
} }
public int getSize()
{
return mBuffer.length;
}
public float[] getBuffer() public float[] getBuffer()
{ {
return mBuffer; return mBuffer;

View File

@@ -39,6 +39,8 @@ import source.tuner.TunerGroupNode;
import source.tuner.TunerNode; import source.tuner.TunerNode;
import source.tuner.TunerSelectionListener; import source.tuner.TunerSelectionListener;
import controller.channel.Channel; import controller.channel.Channel;
import controller.channel.ChannelEvent;
import controller.channel.ChannelEvent.Event;
import controller.channel.ChannelNode; import controller.channel.ChannelNode;
import controller.site.Site; import controller.site.Site;
import controller.system.System; import controller.system.System;
@@ -183,6 +185,7 @@ public class ConfigurationControllerModel extends DefaultTreeModel
SourceConfigTuner sourceConfig = new SourceConfigTuner(); SourceConfigTuner sourceConfig = new SourceConfigTuner();
sourceConfig.setFrequency( frequency ); sourceConfig.setFrequency( frequency );
channel.setSourceConfiguration( sourceConfig ); channel.setSourceConfiguration( sourceConfig );
mResourceManager.getChannelManager().channelChanged( new ChannelEvent( channel, Event.CHANNEL_ADDED ) );
channel.setEnabled( true, true ); channel.setEnabled( true, true );
site.addChannel( channel ); site.addChannel( channel );

View File

@@ -714,7 +714,7 @@ public class Channel extends Configuration
} }
catch( SourceException se ) 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(); TunerChannel tunerChannel = getTunerChannel();
return tunerChannel != null && return tunerChannel != null &&
tunerChannel.isWithin( minimum, maximum ); tunerChannel.overlaps( minimum, maximum );
} }
public void addTrafficChannel( String channelID, Channel channel ) public void addTrafficChannel( String channelID, Channel channel )

View File

@@ -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;
}
}

View File

@@ -105,8 +105,8 @@ public class ComplexAutomaticGainControl implements Listener<Complex>,
private static final double AGC_OUT_SCALE = 0.7; private static final double AGC_OUT_SCALE = 0.7;
/* Keep max input and output the same */ /* Keep max input and output the same */
private static final double MAX_AMPLITUDE = 32767.0; //1.0; private static final double MAX_AMPLITUDE = 1.0;
private static final double MAX_MANUAL_AMPLITUDE = 32767.0; //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) */ /* Specifies AGC manual gain in dB if AGC is not active ( 0 to 100 dB) */
private static final double MANUAL_GAIN = 0.0; private static final double MANUAL_GAIN = 0.0;

View File

@@ -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;
}
}

View File

@@ -24,6 +24,7 @@ import java.awt.EventQueue;
import java.awt.Robot; import java.awt.Robot;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
@@ -39,6 +40,7 @@ import javax.swing.JMenuBar;
import javax.swing.JMenuItem; import javax.swing.JMenuItem;
import javax.swing.JOptionPane; import javax.swing.JOptionPane;
import javax.swing.JSeparator; import javax.swing.JSeparator;
import javax.swing.KeyStroke;
import map.MapService; import map.MapService;
import net.miginfocom.swing.MigLayout; import net.miginfocom.swing.MigLayout;
@@ -258,6 +260,11 @@ public class SDRTrunk
fileMenu.add( exitMenu ); fileMenu.add( exitMenu );
JMenuItem screenCaptureItem = new JMenuItem( "Screen Capture" ); 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() screenCaptureItem.addActionListener( new ActionListener()
{ {
@Override @Override

View File

@@ -83,6 +83,7 @@ import module.decode.state.TrafficChannelManager;
import module.decode.tait.Tait1200Decoder; import module.decode.tait.Tait1200Decoder;
import module.decode.tait.Tait1200DecoderPanel; import module.decode.tait.Tait1200DecoderPanel;
import module.decode.tait.Tait1200DecoderState; import module.decode.tait.Tait1200DecoderState;
import module.demodulate.am.AMDemodulatorModule;
import module.demodulate.fm.FMDemodulatorModule; import module.demodulate.fm.FMDemodulatorModule;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -192,8 +193,10 @@ public class DecoderFactory
switch( decodeConfig.getDecoderType() ) switch( decodeConfig.getDecoderType() )
{ {
case AM: case AM:
modules.add( new AMDemodulatorModule() );
modules.add( new AMDecoder( decodeConfig ) ); modules.add( new AMDecoder( decodeConfig ) );
modules.add( new AlwaysUnsquelchedDecoderState( DecoderType.AM, channelName ) ); modules.add( new AlwaysUnsquelchedDecoderState( DecoderType.AM, channelName ) );
modules.add( new AudioModule( recordAudio, REMOVE_DC ) );
break; break;
case NBFM: case NBFM:
modules.add( new AudioModule( recordAudio, NO_REMOVE_DC ) ); modules.add( new AudioModule( recordAudio, NO_REMOVE_DC ) );

View File

@@ -27,8 +27,6 @@ public class AMDecoder extends PrimaryDecoder
public AMDecoder( DecodeConfiguration config ) public AMDecoder( DecodeConfiguration config )
{ {
super( config ); super( config );
//TODO: wire in the am decoder pieces
} }
@Override @Override

View File

@@ -19,6 +19,7 @@ package module.decode.am;
import javax.swing.JLabel; import javax.swing.JLabel;
import net.miginfocom.swing.MigLayout;
import module.decode.state.ChangedAttribute; import module.decode.state.ChangedAttribute;
import module.decode.state.DecoderPanel; import module.decode.state.DecoderPanel;
import module.decode.state.DecoderState; import module.decode.state.DecoderState;
@@ -41,7 +42,12 @@ public class AMDecoderPanel extends DecoderPanel
public void init() public void init()
{ {
super.init();
setLayout( new MigLayout( "insets 0 0 0 0", "[grow,fill]", "[]0[]0[]") );
mProtocol.setFont( mFontDecoder ); mProtocol.setFont( mFontDecoder );
mProtocol.setForeground( mColorLabelDecoder );
add( mProtocol, "wrap" ); add( mProtocol, "wrap" );
} }

View File

@@ -260,9 +260,6 @@ public class C4FMSymbolFilter implements FrequencyCorrectionResetListener,
// .getErrorCorrection() ); // .getErrorCorrection() );
mFrequencyAdjustmentRequested = 0; mFrequencyAdjustmentRequested = 0;
/* reset internal frequency error tracker */
mCoarseFrequencyCorrection = 0;
} }
} }

View File

@@ -149,9 +149,9 @@ public class P25DecoderPanel extends DecoderPanel
{ {
mFromAlias.setText( from.getName() ); 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( mFromAlias.setIcon( mSettingsManager.getImageIcon(
icon, SettingsManager.DEFAULT_ICON_SIZE ) ); icon, SettingsManager.DEFAULT_ICON_SIZE ) );
@@ -180,9 +180,9 @@ public class P25DecoderPanel extends DecoderPanel
{ {
mToAlias.setText( to.getName() ); 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( mToAlias.setIcon( mSettingsManager.getImageIcon(
icon, SettingsManager.DEFAULT_ICON_SIZE ) ); icon, SettingsManager.DEFAULT_ICON_SIZE ) );

View File

@@ -24,6 +24,7 @@ import instrument.tap.stream.FloatBufferTap;
import instrument.tap.stream.FloatTap; import instrument.tap.stream.FloatTap;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -74,8 +75,10 @@ public class P25_C4FMDecoder extends P25Decoder implements IRealBufferListener
super( aliasList ); super( aliasList );
/* Filter demodulated sample buffers */ /* Filter demodulated sample buffers */
mC4FMPreFilter = new RealFIRFilter_RB_RB( FilterFactory.getLowPass( float[] filter = FilterFactory.getLowPass( 48000, 2500, 4000, 80, WindowType.HANNING, true );
48000, 6750, 7500, 60, WindowType.HANNING, true ), 1.0f );
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 /* Issue tuned frequency correction commands, remotely controlled by the
* downstream symbol filter */ * downstream symbol filter */

View File

@@ -6,8 +6,6 @@ import message.IMessageListener;
import message.Message; import message.Message;
import module.Module; import module.Module;
import module.decode.p25.message.ldu.LDUMessage; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -23,6 +21,8 @@ import audio.squelch.ISquelchStateListener;
import audio.squelch.SquelchState; import audio.squelch.SquelchState;
import controller.channel.ChannelEvent; import controller.channel.ChannelEvent;
import controller.channel.IChannelEventListener; import controller.channel.IChannelEventListener;
import dsp.filter.iir.DeemphasisFilter;
import dsp.gain.NonClippingGain;
public class P25AudioModule extends Module implements Listener<Message>, public class P25AudioModule extends Module implements Listener<Message>,
IAudioPacketProvider, IMessageListener, IChannelEventListener, IAudioPacketProvider, IMessageListener, IChannelEventListener,
@@ -43,6 +43,7 @@ public class P25AudioModule extends Module implements Listener<Message>,
private AudioMetadata mAudioMetadata; private AudioMetadata mAudioMetadata;
private ChannelEventListener mChannelEventListener = new ChannelEventListener(); private ChannelEventListener mChannelEventListener = new ChannelEventListener();
private SquelchStateListener mSquelchStateListener = new SquelchStateListener(); private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
private NonClippingGain mGain = new NonClippingGain( 5.0f, 0.95f );
public P25AudioModule( boolean record ) public P25AudioModule( boolean record )
{ {
@@ -89,9 +90,10 @@ public class P25AudioModule extends Module implements Listener<Message>,
} }
/** /**
* Primary inject point for p25 imbe audio frame messages. Each LDU audio * Primary processing method for p25 imbe audio frame messages. Each LDU
* message contains 9 imbe voice frames. If the audio is not encrypted, * audio message contains 9 imbe voice frames. Each frame is converted to
* we insert each audio frame asynchronously into the audio converter. * audio when the JMBE library is loaded, processed by auto-gain, and
* broadcast to the audio packet listener.
*/ */
public void receive( Message message ) public void receive( Message message )
{ {
@@ -101,15 +103,15 @@ public class P25AudioModule extends Module implements Listener<Message>,
{ {
LDUMessage ldu = (LDUMessage)message; LDUMessage ldu = (LDUMessage)message;
/* If we detect a valid LDU message with the encryption flag /* Only process unencrypted audio frames */
* set to true, we toggle the encrypted audio state until we
* receive the first TDU or TDULC message, then reset it. */
if( !( ldu.isValid() && ldu.isEncrypted() ) ) if( !( ldu.isValid() && ldu.isEncrypted() ) )
{ {
for( byte[] frame: ((LDUMessage)message).getIMBEFrames() ) for( byte[] frame: ldu.getIMBEFrames() )
{ {
float[] audio = mAudioConverter.decode( frame ); float[] audio = mAudioConverter.decode( frame );
audio = mGain.apply( audio );
mAudioPacketListener.receive( mAudioPacketListener.receive(
new AudioPacket( audio, mAudioMetadata.copyOf() ) ); new AudioPacket( audio, mAudioMetadata.copyOf() ) );
} }

View File

@@ -58,7 +58,7 @@ public class AMDemodulatorModule extends Module
public AMDemodulatorModule() public AMDemodulatorModule()
{ {
mIQFilter = new ComplexFIRFilter_CB_CB( FilterFactory.getLowPass( 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 ); mDemodulator = new AMDemodulator_CB( 500.0f );
mIQFilter.setListener( mDemodulator ); mIQFilter.setListener( mDemodulator );

View File

@@ -133,7 +133,10 @@ public class FMDemodulatorModule extends Module implements IComplexBufferListene
mDemodulator.dispose(); mDemodulator.dispose();
mDemodulator = null; mDemodulator = null;
mDCRemovalFilter.dispose(); if( mDCRemovalFilter != null )
{
mDCRemovalFilter.dispose();
}
mDCRemovalFilter = null; mDCRemovalFilter = null;
if( mAutomaticFrequencyControl != null ) if( mAutomaticFrequencyControl != null )

View File

@@ -17,10 +17,10 @@
******************************************************************************/ ******************************************************************************/
package record; package record;
import java.io.File; import java.io.File;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -33,6 +33,7 @@ import audio.AudioPacket;
import audio.metadata.Metadata; import audio.metadata.Metadata;
import audio.metadata.MetadataType; import audio.metadata.MetadataType;
import controller.ThreadPoolManager; import controller.ThreadPoolManager;
import controller.ThreadPoolManager.ThreadType;
public class RecorderManager implements Listener<AudioPacket> public class RecorderManager implements Listener<AudioPacket>
{ {
@@ -43,6 +44,8 @@ public class RecorderManager implements Listener<AudioPacket>
private Map<String,RealBufferWaveRecorder> mRecorders = new HashMap<>(); private Map<String,RealBufferWaveRecorder> mRecorders = new HashMap<>();
private ThreadPoolManager mThreadPoolManager; private ThreadPoolManager mThreadPoolManager;
private boolean mCanStartNewRecorders = true;
public RecorderManager( ThreadPoolManager threadPoolManager ) public RecorderManager( ThreadPoolManager threadPoolManager )
{ {
@@ -72,22 +75,35 @@ public class RecorderManager implements Listener<AudioPacket>
} }
else if( audioPacket.getType() == AudioPacket.Type.END ) else if( audioPacket.getType() == AudioPacket.Type.END )
{ {
mRecorders.remove( identifier ); RealBufferWaveRecorder finished = mRecorders.remove( identifier );
recorder.stop(); finished.stop();
} }
} }
else if( audioPacket.getType() == AudioPacket.Type.AUDIO ) else if( audioPacket.getType() == AudioPacket.Type.AUDIO )
{ {
String filePrefix = getFilePrefix( audioPacket ); if( mCanStartNewRecorders )
{
RealBufferWaveRecorder recorder = String filePrefix = getFilePrefix( audioPacket );
new RealBufferWaveRecorder( mThreadPoolManager,
AUDIO_SAMPLE_RATE, filePrefix );
recorder.start();
recorder.receive( audioPacket.getAudioBuffer() ); try
mRecorders.put( identifier, recorder ); {
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 );
}
}
} }
} }
} }

View File

@@ -125,11 +125,10 @@ public class RealBufferWaveRecorder extends Module
public void stop() 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 @Override
@@ -191,7 +190,6 @@ public class RealBufferWaveRecorder extends Module
try try
{ {
mWriter.close(); mWriter.close();
mWriter = null;
} }
catch( IOException io ) catch( IOException io )
{ {
@@ -200,11 +198,12 @@ public class RealBufferWaveRecorder extends Module
} }
} }
mFile = null;
mThreadPoolManager.cancel( mProcessorHandle ); mThreadPoolManager.cancel( mProcessorHandle );
mWriter = null;
mFile = null;
mProcessorHandle = null; mProcessorHandle = null;
mThreadPoolManager = null;
} }
else else
{ {

View File

@@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* SDR Trunk * 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 * 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 * it under the terms of the GNU General Public License as published by
@@ -71,13 +71,17 @@ public class TunerChannel implements Comparable<TunerChannel>
} }
/** /**
* Indicates if any part of this tuner channel is contained within the * Indicates if any part of this tuner channel overlaps the specified range
* minimum and maximum frequency values.
*/ */
public boolean isWithin( long minimum, long maximum ) public boolean overlaps( long minimum, long maximum )
{ {
return ( ( minimum <= getMinFrequency() && getMinFrequency() <= maximum ) || long channelMin = getMinFrequency();
( minimum <= getMaxFrequency() && getMaxFrequency() <= maximum ) ); long channelMax = getMaxFrequency();
return ( ( minimum <= channelMin && channelMin <= maximum ) ||
( minimum <= channelMax && channelMax <= maximum ) ||
( minimum <= channelMin && channelMax <= maximum ) ||
( channelMin <= minimum && maximum <= channelMax ) );
} }
public String toString() public String toString()
@@ -85,9 +89,9 @@ public class TunerChannel implements Comparable<TunerChannel>
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append( "Channel: " ); sb.append( "Channel: " );
sb.append( ( mFrequency - ( mBandwidth / 2 ) ) ); sb.append( getMinFrequency() );
sb.append( "-" ); sb.append( "-" );
sb.append( ( mFrequency + ( mBandwidth / 2 ) ) ); sb.append( getMaxFrequency() );
return sb.toString(); return sb.toString();
} }

View File

@@ -264,6 +264,7 @@ public class TunerChannelSource extends ComplexSource
public class DecimationProcessor implements Runnable public class DecimationProcessor implements Runnable
{ {
private AtomicBoolean mDispose = new AtomicBoolean( false ); private AtomicBoolean mDispose = new AtomicBoolean( false );
private List<ComplexBuffer> mSampleBuffers = new ArrayList<ComplexBuffer>();
public void dispose() public void dispose()
{ {
@@ -278,64 +279,64 @@ public class TunerChannelSource extends ComplexSource
* run the program out of memory */ * run the program out of memory */
try try
{ {
/* Check to see if we've been shutdown */ if( mBuffer != null )
if( mDispose.get() )
{ {
cleanup(); mBuffer.drainTo( mSampleBuffers, 4 );
return;
} for( Buffer buffer: mSampleBuffers )
else
{
List<ComplexBuffer> sampleBuffers =
new ArrayList<ComplexBuffer>();
if( mBuffer != null )
{ {
mBuffer.drainTo( sampleBuffers, 4 ); /* Check to see if we've been shutdown */
if( mDispose.get() )
for( Buffer buffer: sampleBuffers )
{ {
/* Check to see if we've been shutdown */ cleanup();
if( mDispose.get() ) return;
{ }
cleanup(); else
return; {
} float[] samples = buffer.getSamples();
else
{
float[] samples = buffer.getSamples();
/* We make a copy of the buffer so that we don't affect /* We make a copy of the buffer so that we don't affect
* anyone else that is using the same buffer, like other * anyone else that is using the same buffer, like other
* channels or the spectral display */ * channels or the spectral display */
float[] translated = new float[ samples.length ]; float[] translated = new float[ samples.length ];
/* Perform frequency translation */
for( int x = 0; x < samples.length; x += 2 )
{
mMixer.rotate();
/* Perform frequency translation */ translated[ x ] = Complex.multiplyInphase(
for( int x = 0; x < samples.length; x += 2 ) samples[ x ], samples[ x + 1 ], mMixer.inphase(), mMixer.quadrature() );
{
mMixer.rotate(); translated[ x + 1 ] = Complex.multiplyQuadrature(
translated[ x ] = Complex.multiplyInphase(
samples[ x ], samples[ x + 1 ], mMixer.inphase(), mMixer.quadrature() ); 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 ) 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();
} }
} }
} }

View File

@@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* SDR Trunk * 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 * 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 * 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.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator;
import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionException;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -39,25 +38,34 @@ public abstract class TunerController implements Tunable
/* List of currently tuned channels being served to demod channels */ /* List of currently tuned channels being served to demod channels */
protected ArrayList<TunerChannel> mTunedChannels = protected ArrayList<TunerChannel> mTunedChannels =
new ArrayList<TunerChannel>(); new ArrayList<TunerChannel>();
protected FrequencyController mFrequencyController; protected FrequencyController mFrequencyController;
private int mMiddleUnusable;
private double mUsableBandwidthPercentage;
/** /**
* Abstract tuner controller class. The tuner controller manages frequency * Abstract tuner controller class. The tuner controller manages frequency
* bandwidth and currently tuned channels that are being fed samples from * bandwidth and currently tuned channels that are being fed samples from
* the tuner. * the tuner.
* *
* @param minimumFrequency - minimum uncorrected tunable frequency * @param minimumFrequency minimum uncorrected tunable frequency
* @param maximumFrequency - maximum 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 * @throws SourceException - for any issues related to constructing the
* class, tuning a frequency, or setting the bandwidth * 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, mFrequencyController = new FrequencyController( this,
minimumFrequency, minimumFrequency,
maximumFrequency, maximumFrequency,
0.0d ); 0.0d );
mMiddleUnusable = middleUnusable;
mUsableBandwidthPercentage = usableBandwidthPercentage;
} }
/** /**
@@ -112,24 +120,21 @@ public abstract class TunerController implements Tunable
return mFrequencyController.getMaximumFrequency(); return mFrequencyController.getMaximumFrequency();
} }
public long getMinTunedFrequency() private long getMinTunedFrequency() throws SourceException
{ {
return mFrequencyController.getFrequency() - return mFrequencyController.getFrequency() - ( getUsableBandwidth() / 2 );
( mFrequencyController.getBandwidth() / 2 );
} }
public long getMaxTunedFrequency() private long getMaxTunedFrequency() throws SourceException
{ {
return mFrequencyController.getFrequency() + return mFrequencyController.getFrequency() + ( getUsableBandwidth() / 2 );
( mFrequencyController.getBandwidth() / 2 );
} }
/** /**
* Indicates if the tuner can accomodate this new channel frequency and * Indicates if channel along with all of the other currently sourced
* bandwidth, along with all of the existing tuned channels currently in * channels can fit within the tunable bandwidth.
* place.
*/ */
public boolean canTuneChannel( TunerChannel channel ) private boolean canTune( TunerChannel channel )
{ {
//Make sure we're within the tunable frequency range of this tuner //Make sure we're within the tunable frequency range of this tuner
if( getMinFrequency() < channel.getMinFrequency() && 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 //Sort the existing locks and get the min/max locked frequencies
Collections.sort( mTunedChannels ); Collections.sort( mTunedChannels );
int usableBandwidth = getUsableBandwidth();
long minLockedFrequency = mTunedChannels.get( 0 ).getMinFrequency(); long minLockedFrequency = mTunedChannels.get( 0 ).getMinFrequency();
long maxLockedFrequency = mTunedChannels long maxLockedFrequency = mTunedChannels
.get( mTunedChannels.size() - 1 ).getMaxFrequency(); .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() && 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; return true;
} }
//Requested channel is lower than the max locked frequency //Requested channel is lower than the max locked frequency
else if( channel.getMaxFrequency() <= maxLockedFrequency && if( channel.getMinFrequency() <= maxLockedFrequency &&
( maxLockedFrequency - channel.getMinFrequency() ) <= getBandwidth() ) maxLockedFrequency - channel.getMinFrequency() <= usableBandwidth )
{ {
return true; return true;
} }
@@ -166,26 +179,72 @@ public abstract class TunerController implements Tunable
return false; 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, public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
Tuner tuner, TunerChannel tunerChannel ) Tuner tuner, TunerChannel channel ) throws RejectedExecutionException
throws RejectedExecutionException, SourceException
{ {
TunerChannelSource source = null; TunerChannelSource source = null;
if( canTuneChannel( tunerChannel ) ) if( canTune( channel ) )
{ {
mTunedChannels.add( tunerChannel ); try
{
updateLOFrequency(); mTunedChannels.add( channel );
source = new TunerChannelSource( threadPoolManager, if( requiresLOUpdate( channel ) )
tuner, tunerChannel ); {
updateLOFrequency();
}
source = new TunerChannelSource( threadPoolManager,
tuner, channel );
}
catch( SourceException se )
{
mTunedChannels.remove( channel );
source = null;
}
} }
return source; 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 ) public void releaseChannel( TunerChannelSource tunerChannelSource )
{ {
if( tunerChannelSource != null ) if( tunerChannelSource != null )
@@ -194,56 +253,115 @@ public abstract class TunerController implements Tunable
} }
else else
{ {
mLog.error( "Tuner Controller - couldn't find the tuned channel " mLog.error( "Request to release channel that is not in current tuned channels set" );
+ "to release it" );
} }
} }
/** /**
* Sets the Local Oscillator frequency to the middle of the currently * Sets the Local Oscillator frequency accomodate the current set of tuned
* locked frequency range, adjusting the left/right of a channel, if the * channels.
* middle falls within the locked range of any of the channels. Note: this *
* will fail to set the correct frequency if multiple overlapping channel * If there is only a single tuned channel, it is placed immediately to the
* bandwidths are locked in the exact middle of the total locked channel * right of the usable bandwidth right of the central DC spike.
* frequency range. *
* 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 ); Collections.sort( mTunedChannels );
long minLockedFrequency = mTunedChannels.get( 0 ).getMinFrequency(); long frequency = getFrequency();
long maxLockedFrequency = mTunedChannels
.get( mTunedChannels.size() - 1 ).getMaxFrequency(); boolean frequencyValid = true;
long middle = minLockedFrequency + //If there is only 1 channel, position it to the right of center
( ( maxLockedFrequency - minLockedFrequency ) / 2 ); if( mTunedChannels.size() == 1 )
long middleMin = middle - 10000;
long middleMax = middle + 10000;
Iterator<TunerChannel> it = mTunedChannels.iterator();
while( it.hasNext() )
{ {
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 //Start by placing the highest frequency channel at the high end of
//the left or the right of that channel, whichever is closer //the spectrum
if( lock.getMinFrequency() < middleMax && middleMin < lock.getMaxFrequency() ) 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; processingRequired = false;
}
else long minAvoid = frequency - mMiddleUnusable;
{ long maxAvoid = frequency + mMiddleUnusable;
middle = lock.getMaxFrequency() + 10000;
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 );
} }
/** /**

View File

@@ -118,7 +118,7 @@ public class AirspyTuner extends Tuner
@Override @Override
public double getSampleSize() public double getSampleSize()
{ {
return 12.0; return 13.0;
} }
@Override @Override

View File

@@ -21,10 +21,12 @@ import javax.xml.bind.annotation.XmlAttribute;
import source.tuner.TunerConfiguration; import source.tuner.TunerConfiguration;
import source.tuner.TunerType; import source.tuner.TunerType;
import source.tuner.airspy.AirspyTunerController.Gain;
public class AirspyTunerConfiguration extends TunerConfiguration public class AirspyTunerConfiguration extends TunerConfiguration
{ {
private int mSampleRate = AirspyTunerController.DEFAULT_SAMPLE_RATE.getRate(); private int mSampleRate = AirspyTunerController.DEFAULT_SAMPLE_RATE.getRate();
private Gain mGain = AirspyTunerController.LINEARITY_GAIN_DEFAULT;
private int mIFGain = AirspyTunerController.IF_GAIN_DEFAULT; private int mIFGain = AirspyTunerController.IF_GAIN_DEFAULT;
private int mMixerGain = AirspyTunerController.MIXER_GAIN_DEFAULT; private int mMixerGain = AirspyTunerController.MIXER_GAIN_DEFAULT;
private int mLNAGain = AirspyTunerController.LNA_GAIN_DEFAULT; private int mLNAGain = AirspyTunerController.LNA_GAIN_DEFAULT;
@@ -62,6 +64,17 @@ public class AirspyTunerConfiguration extends TunerConfiguration
mSampleRate = sampleRate; mSampleRate = sampleRate;
} }
@XmlAttribute( name = "gain" )
public Gain getGain()
{
return mGain;
}
public void setGain( Gain gain )
{
mGain = gain;
}
@XmlAttribute( name = "if_gain" ) @XmlAttribute( name = "if_gain" )
public int getIFGain() public int getIFGain()
{ {

View File

@@ -49,6 +49,8 @@ import source.SourceException;
import source.tuner.TunerConfiguration; import source.tuner.TunerConfiguration;
import source.tuner.TunerConfigurationAssignment; import source.tuner.TunerConfigurationAssignment;
import source.tuner.TunerType; import source.tuner.TunerType;
import source.tuner.airspy.AirspyTunerController.Gain;
import source.tuner.airspy.AirspyTunerController.GainMode;
import controller.ResourceManager; import controller.ResourceManager;
public class AirspyTunerConfigurationPanel extends JPanel public class AirspyTunerConfigurationPanel extends JPanel
@@ -69,18 +71,28 @@ public class AirspyTunerConfigurationPanel extends JPanel
private JTextField mName; private JTextField mName;
private JComboBox<AirspySampleRate> mSampleRateCombo; private JComboBox<AirspySampleRate> mSampleRateCombo;
private JComboBox mDecimationCombo;
private JLabel mEffectiveRate;
private JComboBox<GainMode> mGainModeCombo;
private JLabel mGainLabel;
private JLabel mGainFillerLabel;
private JLabel mGainValueLabel;
private JSlider mGain;
private JLabel mIFGainLabel; private JLabel mIFGainLabel;
private JLabel mIFGainFillerLabel;
private JLabel mIFGainValueLabel;
private JSlider mIFGain; private JSlider mIFGain;
private JLabel mLNAGainLabel; private JLabel mLNAGainLabel;
private JCheckBox mLNAAGC;
private JLabel mLNAGainValueLabel;
private JSlider mLNAGain; private JSlider mLNAGain;
private JLabel mMixerGainValueLabel;
private JCheckBox mMixerAGC;
private JLabel mMixerGainLabel; private JLabel mMixerGainLabel;
private JSlider mMixerGain; private JSlider mMixerGain;
private JCheckBox mMixerAGC;
private JCheckBox mLNAAGC;
public AirspyTunerConfigurationPanel( ResourceManager resourceManager, public AirspyTunerConfigurationPanel( ResourceManager resourceManager,
AirspyTunerController controller ) AirspyTunerController controller )
@@ -210,8 +222,6 @@ public class AirspyTunerConfigurationPanel extends JPanel
mController.setSampleRate( rate ); mController.setSampleRate( rate );
mSelectedConfig.setSampleRate( rate.getRate() ); mSelectedConfig.setSampleRate( rate.getRate() );
save(); save();
mEffectiveRate.setText( rate.getLabel() );
} }
catch ( LibUsbException | UsbException e1 ) catch ( LibUsbException | UsbException e1 )
{ {
@@ -225,31 +235,102 @@ public class AirspyTunerConfigurationPanel extends JPanel
add( mSampleRateCombo, "span 3,growx" ); 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" ); add( new JSeparator(), "span,growx,push" );
/**
* Gain Mode
*/
add( new JLabel( "Gain Mode:" ) );
mGainModeCombo = new JComboBox<AirspyTunerController.GainMode>( 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 * IF/VGA Gain
*/ */
add( new JLabel( "IF Gain" ) ); mIFGainLabel = new JLabel( "IF Gain" );
add( new JLabel( "" ), "span 2" ); add( mIFGainLabel );
mIFGainFillerLabel = new JLabel( "" );
mIFGainLabel = new JLabel( String.valueOf( mSelectedConfig.getIFGain() ) ); add( mIFGainFillerLabel, "span 2" );
add( mIFGainLabel, "wrap" ); mIFGainValueLabel = new JLabel( String.valueOf( mSelectedConfig.getIFGain() ) );
add( mIFGainValueLabel, "wrap" );
mIFGain = new JSlider( JSlider.HORIZONTAL, mIFGain = new JSlider( JSlider.HORIZONTAL,
AirspyTunerController.IF_GAIN_MIN, AirspyTunerController.IF_GAIN_MIN,
@@ -271,7 +352,7 @@ public class AirspyTunerConfigurationPanel extends JPanel
mController.setIFGain( gain ); mController.setIFGain( gain );
mSelectedConfig.setIFGain( gain ); mSelectedConfig.setIFGain( gain );
save(); save();
mIFGainLabel.setText( String.valueOf( gain ) ); mIFGainValueLabel.setText( String.valueOf( gain ) );
} }
catch ( Exception e ) catch ( Exception e )
{ {
@@ -287,12 +368,14 @@ public class AirspyTunerConfigurationPanel extends JPanel
add( mIFGain, "span,grow" ); add( mIFGain, "span,grow" );
add( new JLabel( "Mixer Gain" ) ); /**
* Mixer Gain
*/
mMixerGainLabel = new JLabel( "Mixer Gain" );
add( mMixerGainLabel );
mMixerAGC = new JCheckBox( "AGC" ); mMixerAGC = new JCheckBox( "AGC" );
mMixerAGC.setSelected( mSelectedConfig.isMixerAGC() ); mMixerAGC.setSelected( mSelectedConfig.isMixerAGC() );
mMixerAGC.addActionListener( new ActionListener() mMixerAGC.addActionListener( new ActionListener()
{ {
@Override @Override
@@ -315,8 +398,8 @@ public class AirspyTunerConfigurationPanel extends JPanel
add( mMixerAGC, "span 2,center" ); add( mMixerAGC, "span 2,center" );
mMixerGainLabel = new JLabel( String.valueOf( mSelectedConfig.getMixerGain() ) ); mMixerGainValueLabel = new JLabel( String.valueOf( mSelectedConfig.getMixerGain() ) );
add( mMixerGainLabel, "wrap" ); add( mMixerGainValueLabel, "wrap" );
mMixerGain = new JSlider( JSlider.HORIZONTAL, mMixerGain = new JSlider( JSlider.HORIZONTAL,
AirspyTunerController.MIXER_GAIN_MIN, AirspyTunerController.MIXER_GAIN_MIN,
@@ -338,7 +421,7 @@ public class AirspyTunerConfigurationPanel extends JPanel
mController.setMixerGain( gain ); mController.setMixerGain( gain );
mSelectedConfig.setMixerGain( gain ); mSelectedConfig.setMixerGain( gain );
save(); save();
mMixerGainLabel.setText( String.valueOf( gain ) ); mMixerGainValueLabel.setText( String.valueOf( gain ) );
} }
catch ( Exception e ) catch ( Exception e )
{ {
@@ -352,13 +435,15 @@ public class AirspyTunerConfigurationPanel extends JPanel
} ); } );
add( mMixerGain, "span,grow" ); add( mMixerGain, "span,grow" );
add( new JLabel( "LNA Gain" ) ); /**
* LNA Gain
*/
mLNAGainLabel = new JLabel( "LNA Gain" );
add( mLNAGainLabel );
mLNAAGC = new JCheckBox( "AGC" ); mLNAAGC = new JCheckBox( "AGC" );
mLNAAGC.setSelected( mSelectedConfig.isLNAAGC() ); mLNAAGC.setSelected( mSelectedConfig.isLNAAGC() );
mLNAAGC.addActionListener( new ActionListener() mLNAAGC.addActionListener( new ActionListener()
{ {
@Override @Override
@@ -381,8 +466,8 @@ public class AirspyTunerConfigurationPanel extends JPanel
add( mLNAAGC, "span 2,center" ); add( mLNAAGC, "span 2,center" );
mLNAGainLabel = new JLabel( String.valueOf( mSelectedConfig.getLNAGain() ) ); mLNAGainValueLabel = new JLabel( String.valueOf( mSelectedConfig.getLNAGain() ) );
add( mLNAGainLabel, "wrap" ); add( mLNAGainValueLabel, "wrap" );
mLNAGain = new JSlider( JSlider.HORIZONTAL, mLNAGain = new JSlider( JSlider.HORIZONTAL,
AirspyTunerController.LNA_GAIN_MIN, AirspyTunerController.LNA_GAIN_MIN,
@@ -404,7 +489,7 @@ public class AirspyTunerConfigurationPanel extends JPanel
mController.setLNAGain( gain ); mController.setLNAGain( gain );
mSelectedConfig.setLNAGain( gain ); mSelectedConfig.setLNAGain( gain );
save(); save();
mLNAGainLabel.setText( String.valueOf( gain ) ); mLNAGainValueLabel.setText( String.valueOf( gain ) );
} }
catch ( Exception e ) catch ( Exception e )
{ {
@@ -418,6 +503,9 @@ public class AirspyTunerConfigurationPanel extends JPanel
} ); } );
add( mLNAGain, "span,grow" ); add( mLNAGain, "span,grow" );
//Set the enabled state of each of the gain controls
updateGainComponents( (GainMode)mGainModeCombo.getSelectedItem() );
/** /**
* Create a new configuration * Create a new configuration
@@ -485,6 +573,34 @@ public class AirspyTunerConfigurationPanel extends JPanel
add( mDeleteConfiguration, "span 2,growx,push" ); 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 * Updates gui controls with the values from the tuner configuration
@@ -507,9 +623,7 @@ public class AirspyTunerConfigurationPanel extends JPanel
{ {
mSampleRateCombo.setSelectedItem( rate ); mSampleRateCombo.setSelectedItem( rate );
} }
//TODO: set decimation and effective rate here
mIFGain.setValue( mSelectedConfig.getIFGain() ); mIFGain.setValue( mSelectedConfig.getIFGain() );
mMixerGain.setValue( mSelectedConfig.getMixerGain() ); mMixerGain.setValue( mSelectedConfig.getMixerGain() );
mLNAGain.setValue( mSelectedConfig.getLNAGain() ); mLNAGain.setValue( mSelectedConfig.getLNAGain() );
@@ -517,6 +631,10 @@ public class AirspyTunerConfigurationPanel extends JPanel
mMixerAGC.setSelected( mSelectedConfig.isMixerAGC() ); mMixerAGC.setSelected( mSelectedConfig.isMixerAGC() );
mLNAAGC.setSelected( mSelectedConfig.isLNAAGC() ); mLNAAGC.setSelected( mSelectedConfig.isLNAAGC() );
Gain gain = mSelectedConfig.getGain();
mGainModeCombo.setSelectedItem( Gain.getGainMode( gain ) );
mGain.setValue( gain.getValue() );
mResourceManager.getSettingsManager().setSelectedTunerConfiguration( mResourceManager.getSettingsManager().setSelectedTunerConfiguration(
TunerType.AIRSPY_R820T, mController.getDeviceInfo().getSerialNumber(), config ); TunerType.AIRSPY_R820T, mController.getDeviceInfo().getSerialNumber(), config );

View File

@@ -4,11 +4,10 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedTransferQueue; import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -29,9 +28,6 @@ import sample.Broadcaster;
import sample.Listener; import sample.Listener;
import sample.complex.ComplexBuffer; import sample.complex.ComplexBuffer;
import source.SourceException; import source.SourceException;
import source.tuner.Tuner;
import source.tuner.TunerChannel;
import source.tuner.TunerChannelSource;
import source.tuner.TunerConfiguration; import source.tuner.TunerConfiguration;
import source.tuner.TunerController; import source.tuner.TunerController;
import controller.ThreadPoolManager; import controller.ThreadPoolManager;
@@ -81,18 +77,23 @@ import dsp.filter.hilbert.HilbertTransform;
public class AirspyTunerController extends TunerController 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_MIN = 0;
public static final int LNA_GAIN_MAX = 14; 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_MIN = 0;
public static final int MIXER_GAIN_MAX = 15; 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_MIN = 0;
public static final int IF_GAIN_MAX = 15; 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_MIN = 24000000l;
public static final long FREQUENCY_MAX = 1800000000l; public static final long FREQUENCY_MAX = 1800000000l;
public static final long FREQUENCY_DEFAULT = 101100000; public static final long FREQUENCY_DEFAULT = 101100000;
public static final double USABLE_BANDWIDTH_PERCENT = 0.90;
public static final AirspySampleRate DEFAULT_SAMPLE_RATE = public static final AirspySampleRate DEFAULT_SAMPLE_RATE =
new AirspySampleRate( 0, 10000000, "10.00 MHz" ); new AirspySampleRate( 0, 10000000, "10.00 MHz" );
@@ -136,7 +137,7 @@ public class AirspyTunerController extends TunerController
public AirspyTunerController( Device device, ThreadPoolManager threadPoolManager ) public AirspyTunerController( Device device, ThreadPoolManager threadPoolManager )
throws SourceException throws SourceException
{ {
super( FREQUENCY_MIN, FREQUENCY_MAX ); super( FREQUENCY_MIN, FREQUENCY_MAX, 0, USABLE_BANDWIDTH_PERCENT );
mDevice = device; mDevice = device;
mThreadPoolManager = threadPoolManager; mThreadPoolManager = threadPoolManager;
@@ -295,6 +296,11 @@ public class AirspyTunerController extends TunerController
setMixerAGC( airspy.isMixerAGC() ); setMixerAGC( airspy.isMixerAGC() );
setLNAAGC( airspy.isLNAAGC() ); 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 ) catch( Exception e )
{ {
@@ -481,6 +487,18 @@ public class AirspyTunerController extends TunerController
throw new UsbException( "Couldnt set LNA AGC enabled: " + enabled ); 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 * Sets LNA gain
@@ -597,8 +615,6 @@ public class AirspyTunerController extends TunerController
{ {
int count = EndianUtils.readSwappedInteger( rawCount, 0 ); int count = EndianUtils.readSwappedInteger( rawCount, 0 );
mLog.info( "There are [" + count + "] sample rates available" );
byte[] rawRates = readArray( Command.GET_SAMPLE_RATES, 0, byte[] rawRates = readArray( Command.GET_SAMPLE_RATES, 0,
count, ( count * 4 ) ); count, ( count * 4 ) );
@@ -830,90 +846,154 @@ public class AirspyTunerController extends TunerController
LibUsb.ERROR_NO_DEVICE ); 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(); public enum GainMode
long maxLockedFrequency = mTunedChannels {
.get( mTunedChannels.size() - 1 ).getMaxFrequency(); LINEARITY,
SENSITIVITY,
if( channel.getMinFrequency() >= minLockedFrequency && CUSTOM;
channel.getMaxFrequency() <= maxLockedFrequency ) }
{
return true; 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 ) private int mValue;
{ private int mIF;
return ( maxLockedFrequency - channel.getMinFrequency() ) < getBandwidth() - 1E5; 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<Gain> getLinearityGains()
{
return EnumSet.range( LINEARITY_1, LINEARITY_22 );
}
public static EnumSet<Gain> 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" );
}
}
/** /**

View File

@@ -46,6 +46,9 @@ public abstract class FCDTunerController extends TunerController
private final static Logger mLog = private final static Logger mLog =
LoggerFactory.getLogger( FCDTunerController.class ); 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_INTERFACE = (byte)0x2;
public final static byte FCD_ENDPOINT_IN = (byte)0x82; public final static byte FCD_ENDPOINT_IN = (byte)0x82;
public final static byte FCD_ENDPOINT_OUT = (byte)0x2; public final static byte FCD_ENDPOINT_OUT = (byte)0x2;
@@ -70,7 +73,10 @@ public abstract class FCDTunerController extends TunerController
int minTunableFrequency, int minTunableFrequency,
int maxTunableFrequency ) int maxTunableFrequency )
{ {
super( minTunableFrequency, maxTunableFrequency ); super( minTunableFrequency,
maxTunableFrequency,
DC_SPIKE_AVOID_BUFFER,
USABLE_BANDWIDTH_PERCENT );
mDevice = device; mDevice = device;
mDeviceDescriptor = descriptor; mDeviceDescriptor = descriptor;

View File

@@ -108,6 +108,8 @@ public class HackRFTunerController extends TunerController
public static final long MIN_FREQUENCY = 10000000l; public static final long MIN_FREQUENCY = 10000000l;
public static final long MAX_FREQUENCY = 6000000000l; public static final long MAX_FREQUENCY = 6000000000l;
public static final long DEFAULT_FREQUENCY = 101100000; 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; public final static int TRANSFER_BUFFER_POOL_SIZE = 16;
@@ -131,7 +133,7 @@ public class HackRFTunerController extends TunerController
public HackRFTunerController( Device device, DeviceDescriptor descriptor, public HackRFTunerController( Device device, DeviceDescriptor descriptor,
ThreadPoolManager threadPoolManager ) throws SourceException ThreadPoolManager threadPoolManager ) throws SourceException
{ {
super( MIN_FREQUENCY, MAX_FREQUENCY ); super( MIN_FREQUENCY, MAX_FREQUENCY, DC_SPIKE_AVOID_BUFFER, USABLE_BANDWIDTH_PERCENT );
mDevice = device; mDevice = device;
mDeviceDescriptor = descriptor; mDeviceDescriptor = descriptor;

View File

@@ -137,9 +137,14 @@ public abstract class RTL2832TunerController extends TunerController
DeviceDescriptor deviceDescriptor, DeviceDescriptor deviceDescriptor,
ThreadPoolManager threadPoolManager, ThreadPoolManager threadPoolManager,
long minTunableFrequency, long minTunableFrequency,
long maxTunableFrequency ) throws SourceException long maxTunableFrequency,
int centerUnusableBandwidth,
double usableBandwidthPercentage ) throws SourceException
{ {
super( minTunableFrequency, maxTunableFrequency ); super( minTunableFrequency,
maxTunableFrequency,
centerUnusableBandwidth,
usableBandwidthPercentage );
mThreadPoolManager = threadPoolManager; mThreadPoolManager = threadPoolManager;

View File

@@ -44,6 +44,8 @@ public class E4KTunerController extends RTL2832TunerController
public static final long MIN_FREQUENCY = 52000000; public static final long MIN_FREQUENCY = 52000000;
public static final long MAX_FREQUENCY = 2200000000l; 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 /* The local oscillator is defined by whole (integer) units of the oscillator
* frequency and fractional units representing 1/65536th of the oscillator * frequency and fractional units representing 1/65536th of the oscillator
@@ -115,8 +117,8 @@ public class E4KTunerController extends RTL2832TunerController
ThreadPoolManager threadPoolManager ) ThreadPoolManager threadPoolManager )
throws SourceException throws SourceException
{ {
super( device, deviceDescriptor, threadPoolManager, super( device, deviceDescriptor, threadPoolManager, MIN_FREQUENCY,
MIN_FREQUENCY, MAX_FREQUENCY ); MAX_FREQUENCY, DC_SPIKE_AVOID_BUFFER, USABLE_BANDWIDTH_PERCENT );
} }
@Override @Override

View File

@@ -47,6 +47,9 @@ public class R820TTunerController extends RTL2832TunerController
public static final long MIN_FREQUENCY = 3180000; public static final long MIN_FREQUENCY = 3180000;
public static final long MAX_FREQUENCY = 1782030000; 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 int R820T_IF_FREQUENCY = 3570000;
public static final byte VERSION = (byte)49; public static final byte VERSION = (byte)49;
@@ -78,8 +81,8 @@ public class R820TTunerController extends RTL2832TunerController
ThreadPoolManager threadPoolManager ) ThreadPoolManager threadPoolManager )
throws SourceException throws SourceException
{ {
super( device, deviceDescriptor, threadPoolManager, super( device, deviceDescriptor, threadPoolManager, MIN_FREQUENCY,
MIN_FREQUENCY, MAX_FREQUENCY ); MAX_FREQUENCY, DC_SPIKE_AVOID_BUFFER, USABLE_BANDWIDTH_PERCENT );
} }
@Override @Override

View File

@@ -36,7 +36,8 @@ import source.IControllableFileSource;
import source.IFrameLocationListener; import source.IFrameLocationListener;
import source.RealSource; import source.RealSource;
public class RealWaveSource extends RealSource implements IControllableFileSource public class RealWaveSource extends RealSource
implements IControllableFileSource, AutoCloseable
{ {
private final static Logger mLog = private final static Logger mLog =
LoggerFactory.getLogger( RealWaveSource.class ); LoggerFactory.getLogger( RealWaveSource.class );
@@ -168,6 +169,11 @@ public class RealWaveSource extends RealSource implements IControllableFileSourc
{ {
if( samplesRead < buffer.length ) if( samplesRead < buffer.length )
{ {
if( samplesRead == -1 )
{
throw new IOException( "End of recording" );
}
buffer = Arrays.copyOf( buffer, samplesRead ); buffer = Arrays.copyOf( buffer, samplesRead );
} }

View File

@@ -492,37 +492,4 @@ public class ChannelSpectrumPanel extends JPanel
{ {
mSpectrumPanel.setSmoothingType( type ); mSpectrumPanel.setSmoothingType( type );
} }
public class SampleAssembler implements RealSampleListener
{
private Listener<ComplexBuffer> mListener;
public SampleAssembler( Listener<ComplexBuffer> 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 ];
}
}
}
}
} }

View File

@@ -75,7 +75,7 @@ public class OverlayPanel extends JPanel
} }
private final static BasicStroke DASHED_STROKE = new BasicStroke(0.8f, 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 static DecimalFormat CURSOR_FORMAT = new DecimalFormat( "000.00000" );
private long mFrequency = 0; private long mFrequency = 0;
@@ -342,7 +342,7 @@ public class OverlayPanel extends JPanel
{ {
Stroke currentStroke = graphics.getStroke(); Stroke currentStroke = graphics.getStroke();
graphics.setStroke( DASHED_STROKE ); // graphics.setStroke( DASHED_STROKE );
long minFrequency = getMinDisplayFrequency(); long minFrequency = getMinDisplayFrequency();
long maxFrequency = getMaxDisplayFrequency(); long maxFrequency = getMaxDisplayFrequency();
@@ -352,6 +352,12 @@ public class OverlayPanel extends JPanel
int major = mLabelSizeMonitor.getMajorTickIncrement( graphics ); int major = mLabelSizeMonitor.getMajorTickIncrement( graphics );
int minor = mLabelSizeMonitor.getMinorTickIncrement( 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 //Adjust the start frequency to a multiple of the minor tick spacing
long frequency = minFrequency - ( minFrequency % minor ); long frequency = minFrequency - ( minFrequency % minor );
@@ -373,7 +379,7 @@ public class OverlayPanel extends JPanel
frequency += minor; frequency += minor;
} }
graphics.setStroke( currentStroke ); // graphics.setStroke( currentStroke );
} }
/** /**

View File

@@ -28,6 +28,7 @@ import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelEvent;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Hashtable; import java.util.Hashtable;
import javax.swing.JCheckBoxMenuItem; import javax.swing.JCheckBoxMenuItem;

View File

@@ -189,7 +189,7 @@ public class SpectrumPanel extends JPanel
//Define the gradient //Define the gradient
GradientPaint gradient = new GradientPaint( 0, GradientPaint gradient = new GradientPaint( 0,
getSize().height / 4, ( getSize().height - mSpectrumInset ) / 2,
mColorSpectrumGradientTop, mColorSpectrumGradientTop,
0, 0,
getSize().height, getSize().height,