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-core-1.1.2.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"/>
</classpath>

View File

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

View File

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

View File

@@ -275,7 +275,7 @@ public class AudioManager implements Listener<AudioPacket>, 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<AudioPacket>, 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<AudioPacket>, IAudioController
endAllAudioAssignments();
for( Entry<Integer,AudioChannelAssignment> e: mChannelAssignments.entrySet() )
{
e.getValue().getAudioOutput().dispose();
}
mChannelAssignments.clear();
for( AudioOutput output: mAvailableAudioOutputs )
{
output.dispose();
}
mAvailableAudioOutputs.clear();
mChannelNames.clear();
mAudioOutputMap.clear();

View File

@@ -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 )

View File

@@ -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 )

View File

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

View File

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

View File

@@ -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 )

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;
/* 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;

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.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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 */

View File

@@ -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<Message>,
IAudioPacketProvider, IMessageListener, IChannelEventListener,
@@ -43,6 +43,7 @@ public class P25AudioModule extends Module implements Listener<Message>,
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<Message>,
}
/**
* 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<Message>,
{
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() ) );
}

View File

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

View File

@@ -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 )

View File

@@ -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<AudioPacket>
{
@@ -43,6 +44,8 @@ public class RecorderManager implements Listener<AudioPacket>
private Map<String,RealBufferWaveRecorder> mRecorders = new HashMap<>();
private ThreadPoolManager mThreadPoolManager;
private boolean mCanStartNewRecorders = true;
public RecorderManager( ThreadPoolManager threadPoolManager )
{
@@ -72,22 +75,35 @@ public class RecorderManager implements Listener<AudioPacket>
}
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 );
}
}
}
}
}

View File

@@ -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
{

View File

@@ -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<TunerChannel>
}
/**
* 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<TunerChannel>
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();
}

View File

@@ -264,6 +264,7 @@ public class TunerChannelSource extends ComplexSource
public class DecimationProcessor implements Runnable
{
private AtomicBoolean mDispose = new AtomicBoolean( false );
private List<ComplexBuffer> mSampleBuffers = new ArrayList<ComplexBuffer>();
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<ComplexBuffer> sampleBuffers =
new ArrayList<ComplexBuffer>();
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();
}
}
}

View File

@@ -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<TunerChannel> mTunedChannels =
new ArrayList<TunerChannel>();
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<TunerChannel> 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 );
}
/**

View File

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

View File

@@ -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()
{

View File

@@ -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<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 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<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
*/
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 );

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -492,37 +492,4 @@ public class ChannelSpectrumPanel extends JPanel
{
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,
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 );
}
/**

View File

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

View File

@@ -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,