mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
Adds airspy USB tuner support and updates the spectral display
component. -Airspy supported with 10 and 2.5 MSps sample rates. Additional decimation may be added later. -Spectral display updated to include smoothing and filtering options. Note: the decimated output of a tuner channel from the airspy is 48,076.9 samples per second, versus the normal 48 kHz decimated rate. This is 1.0015 times the normal rate and doesn't appear to impact the decoders or audio processing. Therefore, no fractional resampling has been added.
This commit is contained in:
@@ -28,7 +28,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.Listener;
|
||||
import sample.adapter.SampleAdapter;
|
||||
import sample.adapter.ISampleAdapter;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import source.SourceException;
|
||||
|
||||
@@ -45,7 +45,7 @@ public class ComplexMixer
|
||||
private AudioFormat mAudioFormat;
|
||||
private String mName;
|
||||
private int mBytesPerFrame = 0;
|
||||
private SampleAdapter mSampleAdapter;
|
||||
private ISampleAdapter mSampleAdapter;
|
||||
private Listener<ComplexBuffer> mListener;
|
||||
|
||||
/**
|
||||
@@ -68,7 +68,7 @@ public class ComplexMixer
|
||||
public ComplexMixer( TargetDataLine targetDataLine,
|
||||
AudioFormat format,
|
||||
String name,
|
||||
SampleAdapter sampleAdapter,
|
||||
ISampleAdapter sampleAdapter,
|
||||
Listener<ComplexBuffer> listener )
|
||||
{
|
||||
mTargetDataLine = targetDataLine;
|
||||
|
||||
@@ -31,7 +31,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import dsp.gain.AutomaticGainControl;
|
||||
import sample.Listener;
|
||||
import sample.adapter.SampleAdapter;
|
||||
import sample.adapter.ISampleAdapter;
|
||||
import sample.real.RealBuffer;
|
||||
import source.RealSource;
|
||||
import source.SourceException;
|
||||
@@ -49,7 +49,7 @@ public class RealMixerSource extends RealSource
|
||||
private AudioFormat mAudioFormat;
|
||||
private String mDisplayName;
|
||||
private int mBytesPerFrame = 0;
|
||||
private SampleAdapter mSampleAdapter;
|
||||
private ISampleAdapter mSampleAdapter;
|
||||
private AutomaticGainControl mAGC = new AutomaticGainControl();
|
||||
|
||||
CopyOnWriteArrayList<Listener<RealBuffer>> mSampleListeners =
|
||||
@@ -74,7 +74,7 @@ public class RealMixerSource extends RealSource
|
||||
public RealMixerSource( TargetDataLine targetDataLine,
|
||||
AudioFormat format,
|
||||
String name,
|
||||
SampleAdapter sampleAdapter )
|
||||
ISampleAdapter sampleAdapter )
|
||||
{
|
||||
super( name );
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.Listener;
|
||||
import sample.adapter.SampleAdapter;
|
||||
import sample.adapter.ISampleAdapter;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import sample.complex.ComplexSampleListListener;
|
||||
import source.mixer.ComplexMixer;
|
||||
@@ -48,7 +48,7 @@ public abstract class MixerTuner extends Tuner implements Listener<ComplexBuffer
|
||||
public MixerTuner( String name,
|
||||
MixerTunerType mixerTunerType,
|
||||
TargetDataLine targetDataLine,
|
||||
SampleAdapter sampleAdapter )
|
||||
ISampleAdapter sampleAdapter )
|
||||
{
|
||||
super( name );
|
||||
|
||||
@@ -63,7 +63,7 @@ public abstract class MixerTuner extends Tuner implements Listener<ComplexBuffer
|
||||
|
||||
public MixerTuner( String name,
|
||||
MixerTunerDataLine mixerTuner,
|
||||
SampleAdapter sampleAdapter )
|
||||
ISampleAdapter sampleAdapter )
|
||||
{
|
||||
this( name,
|
||||
mixerTuner.getMixerTunerType(),
|
||||
|
||||
@@ -124,6 +124,11 @@ public abstract class Tuner implements FrequencyChangeBroadcaster,
|
||||
* @return
|
||||
*/
|
||||
public abstract int getSampleRate();
|
||||
|
||||
/**
|
||||
* Sample size in bits
|
||||
*/
|
||||
public abstract double getSampleSize();
|
||||
|
||||
/**
|
||||
* Tuner Local Oscillator Frequency
|
||||
|
||||
@@ -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
|
||||
@@ -28,6 +28,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.Buffer;
|
||||
import sample.Listener;
|
||||
import sample.complex.Complex;
|
||||
import sample.complex.ComplexBuffer;
|
||||
@@ -36,12 +37,12 @@ import source.SourceException;
|
||||
import source.tuner.frequency.FrequencyChangeEvent;
|
||||
import source.tuner.frequency.FrequencyChangeEvent.Attribute;
|
||||
import source.tuner.frequency.FrequencyChangeListener;
|
||||
import util.Oscillator;
|
||||
import controller.ThreadPoolManager;
|
||||
import controller.ThreadPoolManager.ThreadType;
|
||||
import dsp.filter.FilterFactory;
|
||||
import dsp.filter.Window.WindowType;
|
||||
import dsp.filter.cic.ComplexPrimeCICDecimate;
|
||||
import dsp.mixer.Oscillator;
|
||||
|
||||
public class TunerChannelSource extends ComplexSource
|
||||
implements FrequencyChangeListener,
|
||||
@@ -292,7 +293,7 @@ public class TunerChannelSource extends ComplexSource
|
||||
{
|
||||
mBuffer.drainTo( sampleBuffers, 4 );
|
||||
|
||||
for( ComplexBuffer buffer: sampleBuffers )
|
||||
for( Buffer buffer: sampleBuffers )
|
||||
{
|
||||
/* Check to see if we've been shutdown */
|
||||
if( mDispose.get() )
|
||||
|
||||
@@ -21,6 +21,7 @@ import javax.usb.UsbDeviceDescriptor;
|
||||
|
||||
public enum TunerClass
|
||||
{
|
||||
AIRSPY( TunerType.AIRSPY_R820T, "1D50", "60A1", "Airspy.com", "Airspy" ),
|
||||
GENERIC_2832( TunerType.RTL2832_VARIOUS, "0BDA", "2832", "RTL2832", "SDR" ),
|
||||
GENERIC_2838( TunerType.RTL2832_VARIOUS, "0BDA", "2838", "RTL2832", "SDR" ),
|
||||
COMPRO_VIDEOMATE_U620F( TunerType.ELONICS_E4000, "185B", "0620", "Compro", "Videomate U620F" ),
|
||||
@@ -180,6 +181,10 @@ public enum TunerClass
|
||||
{
|
||||
retVal = HACKRF_ONE;
|
||||
}
|
||||
else if( productID == 24737 ) //60A1
|
||||
{
|
||||
retVal = AIRSPY;
|
||||
}
|
||||
break;
|
||||
case 8013: //1F4D
|
||||
if( productID == 47107 ) //B803
|
||||
|
||||
@@ -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
|
||||
@@ -35,6 +35,8 @@ import source.SourceException;
|
||||
import source.config.SourceConfigTuner;
|
||||
import source.config.SourceConfiguration;
|
||||
import source.mixer.MixerManager;
|
||||
import source.tuner.airspy.AirspyTuner;
|
||||
import source.tuner.airspy.AirspyTunerController;
|
||||
import source.tuner.fcd.FCDTuner;
|
||||
import source.tuner.fcd.proV1.FCD1TunerController;
|
||||
import source.tuner.fcd.proplusV2.FCD2TunerController;
|
||||
@@ -208,6 +210,8 @@ public class TunerManager
|
||||
|
||||
switch( tunerClass )
|
||||
{
|
||||
case AIRSPY:
|
||||
return initAirspyTuner( device, descriptor );
|
||||
case ETTUS_USRP_B100:
|
||||
return initEttusB100Tuner( device, descriptor );
|
||||
case FUNCUBE_DONGLE_PRO:
|
||||
@@ -250,6 +254,38 @@ public class TunerManager
|
||||
|
||||
return new TunerInitStatus( null, "Unknown Device" );
|
||||
}
|
||||
|
||||
private TunerInitStatus initAirspyTuner( Device device,
|
||||
DeviceDescriptor descriptor )
|
||||
{
|
||||
try
|
||||
{
|
||||
AirspyTunerController airspyController =
|
||||
new AirspyTunerController( device, mResourceManager.getThreadPoolManager() );
|
||||
|
||||
airspyController.init();
|
||||
|
||||
AirspyTuner tuner = new AirspyTuner( airspyController );
|
||||
|
||||
TunerConfiguration config = getTunerConfiguration( tuner );
|
||||
|
||||
if( config != null )
|
||||
{
|
||||
tuner.apply( config );
|
||||
}
|
||||
|
||||
return new TunerInitStatus( tuner, "LOADED" );
|
||||
}
|
||||
catch( SourceException se )
|
||||
{
|
||||
mLog.error( "couldn't construct Airspy controller/tuner", se );
|
||||
|
||||
return new TunerInitStatus( null,
|
||||
"error constructing Airspy tuner controller" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private TunerInitStatus initEttusB100Tuner( Device device,
|
||||
DeviceDescriptor descriptor )
|
||||
|
||||
@@ -19,6 +19,7 @@ package source.tuner;
|
||||
|
||||
public enum TunerType
|
||||
{
|
||||
AIRSPY_R820T( "R820T" ),
|
||||
ELONICS_E4000( "E4000" ),
|
||||
ETTUS_WBX( "WBX" ),
|
||||
ETTUS_VARIOUS( "Ettus Tuner" ),
|
||||
|
||||
159
src/source/tuner/airspy/AirspyDeviceInformation.java
Normal file
159
src/source/tuner/airspy/AirspyDeviceInformation.java
Normal file
@@ -0,0 +1,159 @@
|
||||
package source.tuner.airspy;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import source.tuner.airspy.AirspyTunerController.BoardID;
|
||||
|
||||
public class AirspyDeviceInformation
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( AirspyDeviceInformation.class );
|
||||
|
||||
private BoardID mBoardID = BoardID.UNKNOWN;
|
||||
private String mFirmwareVersion = "Unknown";
|
||||
private String mPartNumber = "Unknown";
|
||||
private String mSerialNumber = "Unknown";
|
||||
|
||||
/**
|
||||
* Airspy Device Information - provides parsing and access to device
|
||||
* information components.
|
||||
*/
|
||||
public AirspyDeviceInformation()
|
||||
{
|
||||
}
|
||||
|
||||
public void setBoardID( int boardID )
|
||||
{
|
||||
mBoardID = BoardID.fromValue( boardID );
|
||||
}
|
||||
|
||||
public BoardID getBoardID()
|
||||
{
|
||||
return mBoardID;
|
||||
}
|
||||
|
||||
public void setVersion( byte[] data )
|
||||
{
|
||||
if( data != null && data.length > 0 )
|
||||
{
|
||||
/* Truncate the version string if we have a line break - ASCII 10 */
|
||||
for( int x = 0; x < data.length; x++ )
|
||||
{
|
||||
if( data[ x ] == (byte)10 )
|
||||
{
|
||||
mFirmwareVersion = new String( Arrays.copyOf( data, x ) );
|
||||
mFirmwareVersion = mFirmwareVersion.replace( "AirSpy NOS ", "" );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mFirmwareVersion = new String( data );
|
||||
mFirmwareVersion = mFirmwareVersion.replace( "AirSpy NOS ", "" );
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error( "Error setting airspy version byte data - null:" +
|
||||
( data == null ? " TRUE" : " FALSE length:" + data.length ) );
|
||||
}
|
||||
}
|
||||
|
||||
public String getVersion()
|
||||
{
|
||||
return mFirmwareVersion;
|
||||
}
|
||||
|
||||
public void setPartAndSerialNumber( byte[] data )
|
||||
{
|
||||
if( data != null && data.length == 24 )
|
||||
{
|
||||
//Note: values are byte-reversed (big-endian) 32-bit chunks
|
||||
StringBuilder part = new StringBuilder();
|
||||
|
||||
part.append( format( data[ 3 ] ) );
|
||||
part.append( format( data[ 2 ] ) );
|
||||
part.append( format( data[ 1 ] ) );
|
||||
part.append( format( data[ 0 ] ) );
|
||||
part.append( format( data[ 7 ] ) );
|
||||
part.append( format( data[ 6 ] ) );
|
||||
part.append( format( data[ 5 ] ) );
|
||||
part.append( format( data[ 4 ] ) );
|
||||
|
||||
mPartNumber = part.toString();
|
||||
|
||||
StringBuilder serial = new StringBuilder();
|
||||
|
||||
//Note: current airspy library only exposes 64-bits of serial number
|
||||
//address space, but appears to have room for 128-bits. We won't
|
||||
//add the first 64 bits if it's zeros
|
||||
if( !( data[ 8 ] == 0 && data[ 9 ] == 0 && data[ 10 ] == 0 && data[ 11 ] == 0 ) )
|
||||
{
|
||||
serial.append( format( data[ 11 ] ) );
|
||||
serial.append( format( data[ 10 ] ) );
|
||||
serial.append( format( data[ 9 ] ) );
|
||||
serial.append( format( data[ 8 ] ) );
|
||||
serial.append( "-" );
|
||||
}
|
||||
|
||||
if( !( data[ 12 ] == 0 && data[ 13 ] == 0 && data[ 14 ] == 0 && data[ 15 ] == 0 ) )
|
||||
{
|
||||
serial.append( format( data[ 15 ] ) );
|
||||
serial.append( format( data[ 14 ] ) );
|
||||
serial.append( format( data[ 13 ] ) );
|
||||
serial.append( format( data[ 12 ] ) );
|
||||
serial.append( "-" );
|
||||
}
|
||||
|
||||
serial.append( format( data[ 19 ] ) );
|
||||
serial.append( format( data[ 18 ] ) );
|
||||
serial.append( format( data[ 17 ] ) );
|
||||
serial.append( format( data[ 16 ] ) );
|
||||
serial.append( "-" );
|
||||
serial.append( format( data[ 23 ] ) );
|
||||
serial.append( format( data[ 22 ] ) );
|
||||
serial.append( format( data[ 21 ] ) );
|
||||
serial.append( format( data[ 20 ] ) );
|
||||
|
||||
mSerialNumber = serial.toString();
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error( "Error setting airspy part and serial byte data - null:" +
|
||||
( data == null ? " TRUE" : " FALSE length:" + data.length ) );
|
||||
}
|
||||
}
|
||||
|
||||
private String format( byte value )
|
||||
{
|
||||
return String.format( "%02X", value );
|
||||
}
|
||||
|
||||
public String getSerialNumber()
|
||||
{
|
||||
return mSerialNumber;
|
||||
}
|
||||
|
||||
public String getPartNumber()
|
||||
{
|
||||
return mPartNumber;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( "Airspy Device Information\n" );
|
||||
sb.append( "Board: " );
|
||||
sb.append( getBoardID().getLabel() );
|
||||
sb.append( "\nPart Number: " );
|
||||
sb.append( getPartNumber() );
|
||||
sb.append( "\nSerial Number: " );
|
||||
sb.append( getSerialNumber() );
|
||||
sb.append( "\nFirmware: " );
|
||||
sb.append( getVersion() );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
77
src/source/tuner/airspy/AirspyInformationPanel.java
Normal file
77
src/source/tuner/airspy/AirspyInformationPanel.java
Normal file
@@ -0,0 +1,77 @@
|
||||
package source.tuner.airspy;
|
||||
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
public class AirspyInformationPanel extends JPanel
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private AirspyTunerController mController;
|
||||
|
||||
/**
|
||||
* Displays Airspy firmware, serial number and part identifier
|
||||
*/
|
||||
public AirspyInformationPanel( AirspyTunerController controller )
|
||||
{
|
||||
mController = controller;
|
||||
|
||||
initGUI();
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
setLayout( new MigLayout( "fill,wrap 2",
|
||||
"[right,grow][grow]",
|
||||
"[][][][][grow]" ) );
|
||||
|
||||
AirspyDeviceInformation info = mController.getDeviceInfo();
|
||||
|
||||
add( new JLabel( "Serial:" ) );
|
||||
add( new JLabel( info.getSerialNumber() ) );
|
||||
add( new JLabel( "Firmware:" ) );
|
||||
|
||||
//Split the firmware on spaces and use first value -- expose full
|
||||
//string in a tool tip
|
||||
String[] firmware = info.getVersion().split( " " );
|
||||
|
||||
JLabel firmwareLabel;
|
||||
|
||||
if( firmware.length > 1 )
|
||||
{
|
||||
firmwareLabel = new JLabel( firmware[ 0 ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
firmwareLabel = new JLabel( info.getVersion() );
|
||||
}
|
||||
|
||||
firmwareLabel.setToolTipText( info.getVersion() );
|
||||
|
||||
add( firmwareLabel );
|
||||
add( new JLabel( "Part:" ) );
|
||||
add( new JLabel( info.getPartNumber() ) );
|
||||
add( new JLabel( "Board ID:" ) );
|
||||
add( new JLabel( info.getBoardID().getLabel() ) );
|
||||
}
|
||||
}
|
||||
149
src/source/tuner/airspy/AirspySampleAdapter.java
Normal file
149
src/source/tuner/airspy/AirspySampleAdapter.java
Normal file
@@ -0,0 +1,149 @@
|
||||
package source.tuner.airspy;
|
||||
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.adapter.ISampleAdapter;
|
||||
|
||||
public class AirspySampleAdapter implements ISampleAdapter
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( AirspySampleAdapter.class );
|
||||
|
||||
private static final float SCALE_SIGNED_12_BIT_TO_FLOAT = 1.0f / 2048.0f;
|
||||
|
||||
private boolean mSamplePacking = false;
|
||||
|
||||
/**
|
||||
* Adapter to translate byte buffers received from the airspy tuner into
|
||||
* float buffers for processing.
|
||||
*/
|
||||
public AirspySampleAdapter()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample packing places two 12-bit samples into 3 bytes when enabled or
|
||||
* places two 12-bit samples into 4 bytes when disabled.
|
||||
*
|
||||
* @param enabled
|
||||
*/
|
||||
public void setSamplePacking( boolean enabled )
|
||||
{
|
||||
mSamplePacking = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] convert( byte[] samples )
|
||||
{
|
||||
if( mSamplePacking )
|
||||
{
|
||||
return convertPacked( samples );
|
||||
}
|
||||
else
|
||||
{
|
||||
return convertUnpacked( samples );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the byte array containing unsigned 12-bit short values into
|
||||
* signed float values in the range -1 to 1;
|
||||
*
|
||||
* @param data - byte array of unsigned 16-bit values
|
||||
* @return converted float values
|
||||
*/
|
||||
private float[] convertUnpacked( byte[] data )
|
||||
{
|
||||
float[] samples = new float[ data.length / 2 ];
|
||||
|
||||
int pointer = 0;
|
||||
|
||||
for( int x = 0; x < data.length; x += 2 )
|
||||
{
|
||||
samples[ pointer++ ] = scale( ( data[ x ] & 0xFF ) |
|
||||
( data[ x + 1 ] << 8 ) );
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts every 3 bytes containing a pair of 12-bit unsigned values into
|
||||
* a pair of float values in the range -1 to 1;
|
||||
*
|
||||
* @param data - byte array of unsigned 12-bit values
|
||||
* @return converted float values
|
||||
*/
|
||||
private float[] convertPacked( byte[] data1 )
|
||||
{
|
||||
byte[] data = new byte[ data1.length ];
|
||||
|
||||
//Convert big-endian to little-endian
|
||||
for( int x = 0; x < data1.length; x += 4 )
|
||||
{
|
||||
data[ x ] = data1[ x + 3 ];
|
||||
data[ x + 1 ] = data1[ x + 2 ];
|
||||
data[ x + 2 ] = data1[ x + 1 ];
|
||||
data[ x + 3 ] = data1[ x ];
|
||||
}
|
||||
|
||||
int count = (int)( (float)data.length / 1.5f );
|
||||
|
||||
/* Ensure we have an even number of samples */
|
||||
if( count % 2 == 1 )
|
||||
{
|
||||
count--;
|
||||
}
|
||||
|
||||
int bytes = (int)((float)count * 1.5f);
|
||||
|
||||
float[] samples = new float[ count ];
|
||||
|
||||
int pointer = 0;
|
||||
|
||||
int first;
|
||||
int second;
|
||||
|
||||
for( int x = 0; x < bytes; x += 3 )
|
||||
{
|
||||
first = ( ( data[ x ] << 4 ) & 0xFF0 ) |
|
||||
( ( data[ x + 1 ] >> 4 ) & 0xF );
|
||||
|
||||
samples[ pointer++ ] = scale( first );
|
||||
|
||||
second = ( ( data[ x + 1 ] << 8 ) & 0xF00 ) |
|
||||
( data[ x + 2 ] & 0xFF );
|
||||
|
||||
samples[ pointer++ ] = scale( second );
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts unsigned 12-bit values to signed 12-bit values and then scales
|
||||
* the signed value to a signed float value in range: -1.0 : +1.0
|
||||
*/
|
||||
public static float scale( int value )
|
||||
{
|
||||
return (float)(( value & 0xFFF ) - 2048 ) * SCALE_SIGNED_12_BIT_TO_FLOAT;
|
||||
}
|
||||
}
|
||||
168
src/source/tuner/airspy/AirspyTuner.java
Normal file
168
src/source/tuner/airspy/AirspyTuner.java
Normal file
@@ -0,0 +1,168 @@
|
||||
package source.tuner.airspy;
|
||||
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.Listener;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import source.SourceException;
|
||||
import source.tuner.Tuner;
|
||||
import source.tuner.TunerChannel;
|
||||
import source.tuner.TunerChannelSource;
|
||||
import source.tuner.TunerClass;
|
||||
import source.tuner.TunerConfiguration;
|
||||
import source.tuner.TunerType;
|
||||
import source.tuner.airspy.AirspyTunerController.BoardID;
|
||||
import controller.ResourceManager;
|
||||
import controller.ThreadPoolManager;
|
||||
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
public class AirspyTuner extends Tuner
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( AirspyTuner.class );
|
||||
|
||||
AirspyTunerController mController;
|
||||
|
||||
public AirspyTuner( AirspyTunerController controller )
|
||||
{
|
||||
super( "Airspy" );
|
||||
|
||||
mController = controller;
|
||||
|
||||
/* Register for frequency/sample rate changes so that we can rebroadcast
|
||||
* to any registered listeners */
|
||||
mController.addListener( this );
|
||||
}
|
||||
|
||||
@Override
|
||||
public JPanel getEditor( ResourceManager resourceManager )
|
||||
{
|
||||
return new AirspyTunerEditorPanel( this, resourceManager );
|
||||
}
|
||||
|
||||
public AirspyTunerController getController()
|
||||
{
|
||||
return mController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply( TunerConfiguration config ) throws SourceException
|
||||
{
|
||||
mController.apply( config );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUniqueID()
|
||||
{
|
||||
try
|
||||
{
|
||||
return mController.getDeviceInfo().getSerialNumber();
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
mLog.error( "error gettting serial number", e );
|
||||
}
|
||||
|
||||
return BoardID.AIRSPY.getLabel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunerClass getTunerClass()
|
||||
{
|
||||
return TunerClass.AIRSPY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunerType getTunerType()
|
||||
{
|
||||
return TunerType.AIRSPY_R820T;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSampleRate()
|
||||
{
|
||||
int rate = 0;
|
||||
|
||||
try
|
||||
{
|
||||
rate = mController.getCurrentSampleRate();
|
||||
}
|
||||
catch ( SourceException e )
|
||||
{
|
||||
mLog.debug( "Error while getting sample rate from controller", e );
|
||||
}
|
||||
|
||||
return rate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getSampleSize()
|
||||
{
|
||||
return 12.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFrequency() throws SourceException
|
||||
{
|
||||
return mController.getFrequency();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
||||
TunerChannel channel ) throws RejectedExecutionException, SourceException
|
||||
{
|
||||
//TODO: this channel has a decimated sample rate of:
|
||||
// 10.0 MSps = 10,000,000 / 208 = 48076.923
|
||||
// 5.0 MSps = 5,000,000 / 104 = 48076.923
|
||||
// 2.5 MSps = 2,500,000 / 52 = 48076.923
|
||||
//Consider implementing a fractional resampler to get a correct 48 kHz
|
||||
//output sample rate
|
||||
|
||||
return mController.getChannel( threadPoolManager, this, channel );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseChannel( TunerChannelSource source )
|
||||
{
|
||||
/* Unregister for receiving samples */
|
||||
removeListener( (Listener<ComplexBuffer>)source );
|
||||
|
||||
/* Tell the controller to release the channel and cleanup */
|
||||
if( source != null )
|
||||
{
|
||||
mController.releaseChannel( source );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener( Listener<ComplexBuffer> listener )
|
||||
{
|
||||
mController.addListener( listener );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener( Listener<ComplexBuffer> listener )
|
||||
{
|
||||
mController.removeListener( listener );
|
||||
}
|
||||
}
|
||||
119
src/source/tuner/airspy/AirspyTunerConfiguration.java
Normal file
119
src/source/tuner/airspy/AirspyTunerConfiguration.java
Normal file
@@ -0,0 +1,119 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package source.tuner.airspy;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
|
||||
import source.tuner.TunerConfiguration;
|
||||
import source.tuner.TunerType;
|
||||
|
||||
public class AirspyTunerConfiguration extends TunerConfiguration
|
||||
{
|
||||
private int mSampleRate = AirspyTunerController.DEFAULT_SAMPLE_RATE.getRate();
|
||||
private int mIFGain = AirspyTunerController.IF_GAIN_DEFAULT;
|
||||
private int mMixerGain = AirspyTunerController.MIXER_GAIN_DEFAULT;
|
||||
private int mLNAGain = AirspyTunerController.LNA_GAIN_DEFAULT;
|
||||
|
||||
private boolean mMixerAGC = false;
|
||||
private boolean mLNAAGC = false;
|
||||
|
||||
/**
|
||||
* Default constructor for JAXB
|
||||
*/
|
||||
public AirspyTunerConfiguration()
|
||||
{
|
||||
this( "Default" );
|
||||
}
|
||||
|
||||
public AirspyTunerConfiguration( String name )
|
||||
{
|
||||
super( name );
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunerType getTunerType()
|
||||
{
|
||||
return TunerType.AIRSPY_R820T;
|
||||
}
|
||||
|
||||
@XmlAttribute( name = "sample_rate" )
|
||||
public int getSampleRate()
|
||||
{
|
||||
return mSampleRate;
|
||||
}
|
||||
|
||||
public void setSampleRate( int sampleRate )
|
||||
{
|
||||
mSampleRate = sampleRate;
|
||||
}
|
||||
|
||||
@XmlAttribute( name = "if_gain" )
|
||||
public int getIFGain()
|
||||
{
|
||||
return mIFGain;
|
||||
}
|
||||
|
||||
public void setIFGain( int gain )
|
||||
{
|
||||
mIFGain = gain;
|
||||
}
|
||||
|
||||
@XmlAttribute( name = "mixer_gain" )
|
||||
public int getMixerGain()
|
||||
{
|
||||
return mMixerGain;
|
||||
}
|
||||
|
||||
public void setMixerGain( int gain )
|
||||
{
|
||||
mMixerGain = gain;
|
||||
}
|
||||
|
||||
@XmlAttribute( name = "lna_gain" )
|
||||
public int getLNAGain()
|
||||
{
|
||||
return mLNAGain;
|
||||
}
|
||||
|
||||
public void setLNAGain( int gain )
|
||||
{
|
||||
mLNAGain = gain;
|
||||
}
|
||||
|
||||
@XmlAttribute( name = "mixer_agc" )
|
||||
public boolean isMixerAGC()
|
||||
{
|
||||
return mMixerAGC;
|
||||
}
|
||||
|
||||
public void setMixerAGC( boolean enabled )
|
||||
{
|
||||
mMixerAGC = enabled;
|
||||
}
|
||||
|
||||
@XmlAttribute( name = "lna_agc" )
|
||||
public boolean isLNAAGC()
|
||||
{
|
||||
return mLNAAGC;
|
||||
}
|
||||
|
||||
public void setLNAAGC( boolean enabled )
|
||||
{
|
||||
mLNAAGC = enabled;
|
||||
}
|
||||
}
|
||||
587
src/source/tuner/airspy/AirspyTunerConfigurationPanel.java
Normal file
587
src/source/tuner/airspy/AirspyTunerConfigurationPanel.java
Normal file
@@ -0,0 +1,587 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package source.tuner.airspy;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.FocusListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.ComboBoxModel;
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JSeparator;
|
||||
import javax.swing.JSlider;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import javax.usb.UsbException;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.usb4java.LibUsbException;
|
||||
|
||||
import source.SourceException;
|
||||
import source.tuner.TunerConfiguration;
|
||||
import source.tuner.TunerConfigurationAssignment;
|
||||
import source.tuner.TunerType;
|
||||
import controller.ResourceManager;
|
||||
|
||||
public class AirspyTunerConfigurationPanel extends JPanel
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( AirspyTunerConfigurationPanel.class );
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private ResourceManager mResourceManager;
|
||||
private AirspyTunerController mController;
|
||||
private AirspyTunerConfiguration mSelectedConfig;
|
||||
|
||||
private JButton mNewConfiguration;
|
||||
private JButton mDeleteConfiguration;
|
||||
private JComboBox<AirspyTunerConfiguration> mComboConfigurations;
|
||||
|
||||
private JTextField mName;
|
||||
|
||||
private JComboBox<AirspySampleRate> mSampleRateCombo;
|
||||
private JComboBox mDecimationCombo;
|
||||
private JLabel mEffectiveRate;
|
||||
|
||||
private JLabel mIFGainLabel;
|
||||
private JSlider mIFGain;
|
||||
private JLabel mLNAGainLabel;
|
||||
private JSlider mLNAGain;
|
||||
private JLabel mMixerGainLabel;
|
||||
private JSlider mMixerGain;
|
||||
|
||||
private JCheckBox mMixerAGC;
|
||||
private JCheckBox mLNAAGC;
|
||||
|
||||
public AirspyTunerConfigurationPanel( ResourceManager resourceManager,
|
||||
AirspyTunerController controller )
|
||||
{
|
||||
mResourceManager = resourceManager;
|
||||
mController = controller;
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the gui components
|
||||
*/
|
||||
private void init()
|
||||
{
|
||||
setLayout( new MigLayout( "fill,wrap 4", "[left][grow][grow][right]",
|
||||
"[][][][][][][grow]" ) );
|
||||
|
||||
/**
|
||||
* Tuner configuration selector
|
||||
*/
|
||||
mComboConfigurations = new JComboBox<AirspyTunerConfiguration>();
|
||||
|
||||
mComboConfigurations.setModel( getModel() );
|
||||
|
||||
/* Determine which tuner configuration should be selected/displayed */
|
||||
TunerConfigurationAssignment savedConfig = null;
|
||||
String serial = null;
|
||||
|
||||
serial = mController.getDeviceInfo().getSerialNumber();
|
||||
|
||||
if( serial != null )
|
||||
{
|
||||
savedConfig = mResourceManager
|
||||
.getSettingsManager().getSelectedTunerConfiguration(
|
||||
TunerType.AIRSPY_R820T, serial );
|
||||
}
|
||||
|
||||
if( savedConfig != null )
|
||||
{
|
||||
mSelectedConfig = getNamedConfiguration(
|
||||
savedConfig.getTunerConfigurationName() );
|
||||
}
|
||||
|
||||
/* If we couldn't determine the saved/selected config, use the first one */
|
||||
if( mSelectedConfig == null )
|
||||
{
|
||||
mSelectedConfig = mComboConfigurations.getItemAt( 0 );
|
||||
|
||||
//Store this config as the default for this tuner at this address
|
||||
|
||||
if( serial != null )
|
||||
{
|
||||
mResourceManager.getSettingsManager()
|
||||
.setSelectedTunerConfiguration( TunerType.AIRSPY_R820T,
|
||||
serial, mSelectedConfig );
|
||||
}
|
||||
}
|
||||
|
||||
mComboConfigurations.setSelectedItem( mSelectedConfig );
|
||||
|
||||
mComboConfigurations.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
AirspyTunerConfiguration selected =
|
||||
(AirspyTunerConfiguration)mComboConfigurations
|
||||
.getItemAt( mComboConfigurations.getSelectedIndex() );
|
||||
|
||||
if( selected != null )
|
||||
{
|
||||
update( selected );
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
add( new JLabel( "Config:" ) );
|
||||
add( mComboConfigurations, "span,growx,push" );
|
||||
|
||||
add( new JSeparator(), "span,growx,push" );
|
||||
|
||||
|
||||
/**
|
||||
* Tuner Configuration Name
|
||||
*/
|
||||
mName = new JTextField();
|
||||
mName.setText( mSelectedConfig.getName() );
|
||||
|
||||
mName.addFocusListener( new FocusListener()
|
||||
{
|
||||
@Override
|
||||
public void focusLost( FocusEvent e )
|
||||
{
|
||||
mSelectedConfig.setName( mName.getText() );
|
||||
save();
|
||||
}
|
||||
@Override
|
||||
public void focusGained( FocusEvent e ) {}
|
||||
} );
|
||||
|
||||
add( new JLabel( "Name:" ) );
|
||||
add( mName, "span,growx,push" );
|
||||
|
||||
/**
|
||||
* Sample Rate
|
||||
*/
|
||||
add( new JLabel( "Sample Rate:" ) );
|
||||
|
||||
List<AirspySampleRate> rates = mController.getSampleRates();
|
||||
|
||||
mSampleRateCombo = new JComboBox<AirspySampleRate>(
|
||||
new DefaultComboBoxModel<AirspySampleRate>( rates.toArray(
|
||||
new AirspySampleRate[ rates.size() ] ) ) );
|
||||
|
||||
mSampleRateCombo.setSelectedItem( mController.getSampleRate() );
|
||||
|
||||
mSampleRateCombo.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
AirspySampleRate rate = (AirspySampleRate)mSampleRateCombo.getSelectedItem();
|
||||
|
||||
try
|
||||
{
|
||||
mController.setSampleRate( rate );
|
||||
mSelectedConfig.setSampleRate( rate.getRate() );
|
||||
save();
|
||||
|
||||
mEffectiveRate.setText( rate.getLabel() );
|
||||
}
|
||||
catch ( LibUsbException | UsbException e1 )
|
||||
{
|
||||
JOptionPane.showMessageDialog( AirspyTunerConfigurationPanel.this,
|
||||
"Couldn't set sample rate to " + rate.getLabel() );
|
||||
|
||||
mLog.error( "Error setting airspy sample rate", e1 );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
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" );
|
||||
|
||||
/**
|
||||
* IF/VGA Gain
|
||||
*/
|
||||
add( new JLabel( "IF Gain" ) );
|
||||
add( new JLabel( "" ), "span 2" );
|
||||
|
||||
mIFGainLabel = new JLabel( String.valueOf( mSelectedConfig.getIFGain() ) );
|
||||
add( mIFGainLabel, "wrap" );
|
||||
|
||||
mIFGain = new JSlider( JSlider.HORIZONTAL,
|
||||
AirspyTunerController.IF_GAIN_MIN,
|
||||
AirspyTunerController.IF_GAIN_MAX,
|
||||
mSelectedConfig.getIFGain() );
|
||||
|
||||
mIFGain.setMajorTickSpacing( 1 );
|
||||
mIFGain.setPaintTicks( true );
|
||||
|
||||
mIFGain.addChangeListener( new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged( ChangeEvent event )
|
||||
{
|
||||
int gain = mIFGain.getValue();
|
||||
|
||||
try
|
||||
{
|
||||
mController.setIFGain( gain );
|
||||
mSelectedConfig.setIFGain( gain );
|
||||
save();
|
||||
mIFGainLabel.setText( String.valueOf( gain ) );
|
||||
}
|
||||
catch ( Exception e )
|
||||
{
|
||||
mLog.error( "Couldn't set airspy IF gain to:" + gain, e );
|
||||
|
||||
JOptionPane.showMessageDialog( mIFGain,
|
||||
"Couldn't set IF gain value to " + gain );
|
||||
|
||||
mIFGain.setValue( mSelectedConfig.getIFGain() );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
add( mIFGain, "span,grow" );
|
||||
|
||||
add( new JLabel( "Mixer Gain" ) );
|
||||
|
||||
mMixerAGC = new JCheckBox( "AGC" );
|
||||
|
||||
mMixerAGC.setSelected( mSelectedConfig.isMixerAGC() );
|
||||
|
||||
mMixerAGC.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
try
|
||||
{
|
||||
mController.setMixerAGC( !mSelectedConfig.isMixerAGC() );
|
||||
mSelectedConfig.setMixerAGC( !mSelectedConfig.isMixerAGC() );
|
||||
save();
|
||||
}
|
||||
catch ( Exception e1 )
|
||||
{
|
||||
mLog.error( "Error setting Mixer AGC Enabled:" + !mSelectedConfig.isMixerAGC() );
|
||||
mMixerAGC.setSelected( mSelectedConfig.isMixerAGC() );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
|
||||
add( mMixerAGC, "span 2,center" );
|
||||
|
||||
mMixerGainLabel = new JLabel( String.valueOf( mSelectedConfig.getMixerGain() ) );
|
||||
add( mMixerGainLabel, "wrap" );
|
||||
|
||||
mMixerGain = new JSlider( JSlider.HORIZONTAL,
|
||||
AirspyTunerController.MIXER_GAIN_MIN,
|
||||
AirspyTunerController.MIXER_GAIN_MAX,
|
||||
mSelectedConfig.getMixerGain() );
|
||||
|
||||
mMixerGain.setMajorTickSpacing( 1 );
|
||||
mMixerGain.setPaintTicks( true );
|
||||
|
||||
mMixerGain.addChangeListener( new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged( ChangeEvent event )
|
||||
{
|
||||
int gain = mMixerGain.getValue();
|
||||
|
||||
try
|
||||
{
|
||||
mController.setMixerGain( gain );
|
||||
mSelectedConfig.setMixerGain( gain );
|
||||
save();
|
||||
mMixerGainLabel.setText( String.valueOf( gain ) );
|
||||
}
|
||||
catch ( Exception e )
|
||||
{
|
||||
mLog.error( "Couldn't set airspy Mixer gain to:" + gain, e );
|
||||
|
||||
JOptionPane.showMessageDialog( mIFGain, "Couldn't set Mixer gain value to " + gain );
|
||||
|
||||
mMixerGain.setValue( mSelectedConfig.getMixerGain() );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
add( mMixerGain, "span,grow" );
|
||||
|
||||
add( new JLabel( "LNA Gain" ) );
|
||||
|
||||
mLNAAGC = new JCheckBox( "AGC" );
|
||||
|
||||
mLNAAGC.setSelected( mSelectedConfig.isLNAAGC() );
|
||||
|
||||
mLNAAGC.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
try
|
||||
{
|
||||
mController.setLNAAGC( !mSelectedConfig.isLNAAGC() );
|
||||
mSelectedConfig.setLNAAGC( !mSelectedConfig.isLNAAGC() );
|
||||
save();
|
||||
}
|
||||
catch ( Exception e1 )
|
||||
{
|
||||
mLog.error( "Error setting LNA AGC Enabled:" + !mSelectedConfig.isLNAAGC() );
|
||||
mLNAAGC.setSelected( mSelectedConfig.isLNAAGC() );
|
||||
}
|
||||
|
||||
}
|
||||
} );
|
||||
|
||||
add( mLNAAGC, "span 2,center" );
|
||||
|
||||
mLNAGainLabel = new JLabel( String.valueOf( mSelectedConfig.getLNAGain() ) );
|
||||
add( mLNAGainLabel, "wrap" );
|
||||
|
||||
mLNAGain = new JSlider( JSlider.HORIZONTAL,
|
||||
AirspyTunerController.LNA_GAIN_MIN,
|
||||
AirspyTunerController.LNA_GAIN_MAX,
|
||||
mSelectedConfig.getLNAGain() );
|
||||
|
||||
mLNAGain.setMajorTickSpacing( 1 );
|
||||
mLNAGain.setPaintTicks( true );
|
||||
|
||||
mLNAGain.addChangeListener( new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged( ChangeEvent event )
|
||||
{
|
||||
int gain = mLNAGain.getValue();
|
||||
|
||||
try
|
||||
{
|
||||
mController.setLNAGain( gain );
|
||||
mSelectedConfig.setLNAGain( gain );
|
||||
save();
|
||||
mLNAGainLabel.setText( String.valueOf( gain ) );
|
||||
}
|
||||
catch ( Exception e )
|
||||
{
|
||||
mLog.error( "Couldn't set airspy LNA gain to:" + gain, e );
|
||||
|
||||
JOptionPane.showMessageDialog( mIFGain, "Couldn't set LNA gain value to " + gain );
|
||||
|
||||
mLNAGain.setValue( mSelectedConfig.getLNAGain() );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
add( mLNAGain, "span,grow" );
|
||||
|
||||
/**
|
||||
* Create a new configuration
|
||||
*/
|
||||
mNewConfiguration = new JButton( "New" );
|
||||
|
||||
mNewConfiguration.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
TunerConfiguration config =
|
||||
mResourceManager.getSettingsManager()
|
||||
.addNewTunerConfiguration(
|
||||
TunerType.AIRSPY_R820T,
|
||||
"New Configuration" );
|
||||
|
||||
mComboConfigurations.setModel( getModel() );
|
||||
|
||||
mComboConfigurations.setSelectedItem( config );
|
||||
|
||||
repaint();
|
||||
}
|
||||
} );
|
||||
|
||||
add( mNewConfiguration, "span 2,growx,push" );
|
||||
|
||||
/**
|
||||
* Delete the currently selected configuration
|
||||
*/
|
||||
mDeleteConfiguration = new JButton( "Delete" );
|
||||
|
||||
mDeleteConfiguration.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
AirspyTunerConfiguration selected =
|
||||
(AirspyTunerConfiguration)mComboConfigurations
|
||||
.getItemAt( mComboConfigurations.getSelectedIndex() );
|
||||
|
||||
if( selected != null )
|
||||
{
|
||||
int n = JOptionPane.showConfirmDialog(
|
||||
AirspyTunerConfigurationPanel.this,
|
||||
"Are you sure you want to delete '"
|
||||
+ selected.getName() + "'?",
|
||||
"Are you sure?",
|
||||
JOptionPane.YES_NO_OPTION );
|
||||
|
||||
if( n == JOptionPane.YES_OPTION )
|
||||
{
|
||||
mResourceManager.getSettingsManager()
|
||||
.deleteTunerConfiguration( selected );
|
||||
|
||||
mComboConfigurations.setModel( getModel() );
|
||||
|
||||
mComboConfigurations.setSelectedIndex( 0 );
|
||||
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
add( mDeleteConfiguration, "span 2,growx,push" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates gui controls with the values from the tuner configuration
|
||||
* @param config - tuner configuration
|
||||
*/
|
||||
private void update( AirspyTunerConfiguration config )
|
||||
{
|
||||
mSelectedConfig = config;
|
||||
|
||||
try
|
||||
{
|
||||
mController.apply( config );
|
||||
|
||||
mName.setText( config.getName() );
|
||||
|
||||
AirspySampleRate rate = mController.getSampleRate(
|
||||
mSelectedConfig.getSampleRate() );
|
||||
|
||||
if( rate != null )
|
||||
{
|
||||
mSampleRateCombo.setSelectedItem( rate );
|
||||
}
|
||||
|
||||
//TODO: set decimation and effective rate here
|
||||
|
||||
mIFGain.setValue( mSelectedConfig.getIFGain() );
|
||||
mMixerGain.setValue( mSelectedConfig.getMixerGain() );
|
||||
mLNAGain.setValue( mSelectedConfig.getLNAGain() );
|
||||
|
||||
mMixerAGC.setSelected( mSelectedConfig.isMixerAGC() );
|
||||
mLNAAGC.setSelected( mSelectedConfig.isLNAAGC() );
|
||||
|
||||
mResourceManager.getSettingsManager().setSelectedTunerConfiguration(
|
||||
TunerType.AIRSPY_R820T, mController.getDeviceInfo().getSerialNumber(), config );
|
||||
|
||||
save();
|
||||
}
|
||||
catch ( /*UsbException |*/ SourceException e1 )
|
||||
{
|
||||
JOptionPane.showMessageDialog(
|
||||
AirspyTunerConfigurationPanel.this,
|
||||
"Airspy Tuner Controller - couldn't "
|
||||
+ "apply the tuner configuration settings - " +
|
||||
e1.getLocalizedMessage() );
|
||||
|
||||
mLog.error( "Airspy Tuner Controller - couldn't apply "
|
||||
+ "config [" + config.getName() + "]", e1 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a combo box model for the tuner configuration combo component
|
||||
* by assembling a list of the tuner configurations appropriate for this
|
||||
* specific tuner
|
||||
*/
|
||||
private ComboBoxModel<AirspyTunerConfiguration> getModel()
|
||||
{
|
||||
ArrayList<TunerConfiguration> configs =
|
||||
mResourceManager.getSettingsManager()
|
||||
.getTunerConfigurations( TunerType.AIRSPY_R820T );
|
||||
|
||||
DefaultComboBoxModel<AirspyTunerConfiguration> model =
|
||||
new DefaultComboBoxModel<AirspyTunerConfiguration>();
|
||||
|
||||
for( TunerConfiguration config: configs )
|
||||
{
|
||||
model.addElement( (AirspyTunerConfiguration)config );
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a specific (named) tuner configuration
|
||||
*/
|
||||
private AirspyTunerConfiguration getNamedConfiguration( String name )
|
||||
{
|
||||
ArrayList<TunerConfiguration> configs =
|
||||
mResourceManager.getSettingsManager()
|
||||
.getTunerConfigurations( TunerType.AIRSPY_R820T );
|
||||
|
||||
for( TunerConfiguration config: configs )
|
||||
{
|
||||
if( config.getName().contentEquals( name ) )
|
||||
{
|
||||
return (AirspyTunerConfiguration)config;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves current tuner configuration settings
|
||||
*/
|
||||
private void save()
|
||||
{
|
||||
mResourceManager.getSettingsManager().save();
|
||||
}
|
||||
}
|
||||
1257
src/source/tuner/airspy/AirspyTunerController.java
Normal file
1257
src/source/tuner/airspy/AirspyTunerController.java
Normal file
File diff suppressed because it is too large
Load Diff
113
src/source/tuner/airspy/AirspyTunerEditorPanel.java
Normal file
113
src/source/tuner/airspy/AirspyTunerEditorPanel.java
Normal file
@@ -0,0 +1,113 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package source.tuner.airspy;
|
||||
|
||||
import gui.control.JFrequencyControl;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import source.SourceException;
|
||||
import source.tuner.frequency.FrequencyChangeEvent;
|
||||
import source.tuner.frequency.FrequencyChangeEvent.Attribute;
|
||||
import source.tuner.frequency.FrequencyChangeListener;
|
||||
|
||||
import com.jidesoft.swing.JideTabbedPane;
|
||||
|
||||
import controller.ResourceManager;
|
||||
|
||||
public class AirspyTunerEditorPanel extends JPanel
|
||||
implements FrequencyChangeListener
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( AirspyTunerEditorPanel.class );
|
||||
|
||||
private JFrequencyControl mFrequencyControl;
|
||||
|
||||
private AirspyTuner mTuner;
|
||||
private AirspyTunerController mController;
|
||||
private ResourceManager mResourceManager;
|
||||
|
||||
public AirspyTunerEditorPanel( AirspyTuner tuner,
|
||||
ResourceManager resourceManager )
|
||||
{
|
||||
mTuner = tuner;
|
||||
mController = mTuner.getController();
|
||||
mResourceManager = resourceManager;
|
||||
|
||||
initGUI();
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
setLayout( new MigLayout( "fill,wrap 2",
|
||||
"[right,grow][grow]",
|
||||
"[][][][][][grow]" ) );
|
||||
|
||||
add( new JLabel( "Airspy Tuner" ), "span,align center" );
|
||||
|
||||
mFrequencyControl = new JFrequencyControl();
|
||||
|
||||
mFrequencyControl.addListener( this );
|
||||
|
||||
/* Add frequency control as frequency change listener. This creates a
|
||||
* feedback loop, so the control does not rebroadcast the event */
|
||||
mTuner.addListener( mFrequencyControl );
|
||||
|
||||
mFrequencyControl.setFrequency( mController.getFrequency(), false );
|
||||
|
||||
add( mFrequencyControl, "span,align center" );
|
||||
|
||||
JideTabbedPane tabs = new JideTabbedPane();
|
||||
tabs.setFont( this.getFont() );
|
||||
tabs.setForeground( Color.BLACK );
|
||||
|
||||
add( tabs, "span,grow,push" );
|
||||
|
||||
tabs.add( "Config", new AirspyTunerConfigurationPanel(
|
||||
mResourceManager, mTuner.getController() ) );
|
||||
|
||||
tabs.add( "Info", new AirspyInformationPanel( mTuner.getController() ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void frequencyChanged( FrequencyChangeEvent event )
|
||||
{
|
||||
if( event.getAttribute() == Attribute.FREQUENCY )
|
||||
{
|
||||
try
|
||||
{
|
||||
mController.setFrequency( event.getValue().longValue() );
|
||||
}
|
||||
catch ( SourceException e )
|
||||
{
|
||||
mLog.error( "error setting frequency [" +
|
||||
event.getValue().longValue() + "]", e );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,6 +86,12 @@ public class B100Tuner extends Tuner
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getSampleSize()
|
||||
{
|
||||
return 16.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFrequency() throws SourceException
|
||||
{
|
||||
|
||||
@@ -99,6 +99,12 @@ public class FCDTuner extends MixerTuner
|
||||
return (int)mMixerTunerType.getAudioFormat().getSampleRate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getSampleSize()
|
||||
{
|
||||
return 16.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFrequency() throws SourceException
|
||||
{
|
||||
|
||||
@@ -20,7 +20,6 @@ package source.tuner.hackrf;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import javax.usb.UsbException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -35,7 +34,6 @@ import source.tuner.TunerClass;
|
||||
import source.tuner.TunerConfiguration;
|
||||
import source.tuner.TunerType;
|
||||
import source.tuner.hackrf.HackRFTunerController.BoardID;
|
||||
import source.tuner.hackrf.HackRFTunerController.Serial;
|
||||
import controller.ResourceManager;
|
||||
import controller.ThreadPoolManager;
|
||||
|
||||
@@ -53,23 +51,6 @@ public class HackRFTuner extends Tuner
|
||||
|
||||
/* Register for frequency/sample rate changes */
|
||||
mController.addListener( this );
|
||||
|
||||
try
|
||||
{
|
||||
BoardID id = mController.getBoardID();
|
||||
super.setName( id.getLabel() );
|
||||
|
||||
String version = mController.getFirmwareVersion();
|
||||
mLog.info( "HackRF firmware: " + version );
|
||||
|
||||
Serial serial = mController.getSerial();
|
||||
mLog.info( "HackRF Part ID: " + serial.getPartID() );
|
||||
mLog.info( "HackRF Serial Number: " + serial.getSerialNumber() );
|
||||
}
|
||||
catch( UsbException e )
|
||||
{
|
||||
mLog.error( "couldn't read HackRF board id", e );
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
@@ -112,6 +93,12 @@ public class HackRFTuner extends Tuner
|
||||
return mController.getSampleRate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getSampleSize()
|
||||
{
|
||||
return 8.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFrequency() throws SourceException
|
||||
{
|
||||
|
||||
@@ -142,8 +142,21 @@ public class HackRFTunerController extends TunerController
|
||||
{
|
||||
mDeviceHandle = new DeviceHandle();
|
||||
|
||||
LibUsb.open( mDevice, mDeviceHandle );
|
||||
int result = LibUsb.open( mDevice, mDeviceHandle );
|
||||
|
||||
if( result != 0 )
|
||||
{
|
||||
if( result == LibUsb.ERROR_ACCESS )
|
||||
{
|
||||
mLog.error( "Unable to access HackRF - insufficient permissions. "
|
||||
+ "If you are running a Linux OS, have you installed the "
|
||||
+ "hackRF rules file in \\etc\\udev\\rules.d ??" );
|
||||
}
|
||||
|
||||
throw new SourceException( "Couldn't open hackrf device - " +
|
||||
LibUsb.strError( result ) );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
claimInterface();
|
||||
@@ -298,7 +311,7 @@ public class HackRFTunerController extends TunerController
|
||||
*/
|
||||
public long getTunedFrequency() throws SourceException
|
||||
{
|
||||
return 0;
|
||||
return mFrequencyController.getTunedFrequency();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -127,6 +127,12 @@ public class RTL2832Tuner extends Tuner
|
||||
+ "sample rate", e );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getSampleSize()
|
||||
{
|
||||
return 8.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFrequency() throws SourceException
|
||||
|
||||
Reference in New Issue
Block a user