mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-10 23:57:25 -05:00
Merge branch 'master' of https://github.com/DSheirer/sdrtrunk.git
This commit is contained in:
@@ -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>
|
||||
|
||||
29
CHANGELOG
29
CHANGELOG
@@ -1,15 +1,22 @@
|
||||
|
||||
0.2.0 2015xxxx Major overhaul of underlying processing chain and extensive
|
||||
updates to all decoders and filters
|
||||
- Resolved issue with memory leaks and crashes
|
||||
- New audio playback interface with global mute and support for one or two
|
||||
audio channels
|
||||
- Audio playback is prioritized using a new Call Priority alias action
|
||||
with selected channels having the highest priority and any audio without
|
||||
an alias identifier having the lowest priority.
|
||||
- Audio recording by call with new 'Non-Recordable' alias action
|
||||
- (planned)Added Ettus UHD support for B100 with WBX tuner
|
||||
- Added Airspy tuner support
|
||||
0.2.0 2015xxxx
|
||||
- Major overhaul of underlying processing chain and extensive updates to all
|
||||
decoders and filters
|
||||
- New audio playback interface with global mute and support for one or two
|
||||
audio channels
|
||||
- Audio playback is prioritized using a new Call Priority alias action
|
||||
with selected channels having the highest priority and any audio without
|
||||
an alias identifier having the lowest priority.
|
||||
- Updated Audio recording per call with new 'Non-Recordable' alias action
|
||||
- Updated Baseband (I/Q) recording at 48 kHz
|
||||
- Added Airspy tuner support
|
||||
- New traffic channel pool manager for trunked call following
|
||||
- P25 Decoder for C4FM and LSM with trunked call following and audio
|
||||
decoding via the JMBE library
|
||||
- Updated MPT1327 decoder and trunked call following.
|
||||
- Updated spectral and waterfall display with zoom (2-64x) and pan and automatic
|
||||
noise floor (dB) adjusted to tuner sample size
|
||||
- Resolved some issues with memory leaks and crashes
|
||||
|
||||
0.1.5 20150612 Added initial P25 Decoder support with integrated audio decoding
|
||||
using the JMBE library
|
||||
|
||||
Binary file not shown.
@@ -29,6 +29,7 @@ public class AudioEvent
|
||||
AUDIO_UNSQUELCHED,
|
||||
AUDIO_STARTED,
|
||||
AUDIO_STOPPED,
|
||||
AUDIO_CONTINUATION,
|
||||
AUDIO_CONFIGURATION_CHANGE_STARTED,
|
||||
AUDIO_CONFIGURATION_CHANGE_COMPLETE;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -40,6 +40,11 @@ public class FloatCircularBuffer
|
||||
Arrays.fill( mBuffer, (float)0.0 );
|
||||
}
|
||||
|
||||
public int getSize()
|
||||
{
|
||||
return mBuffer.length;
|
||||
}
|
||||
|
||||
public float[] getBuffer()
|
||||
{
|
||||
return mBuffer;
|
||||
|
||||
@@ -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 );
|
||||
|
||||
|
||||
@@ -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 )
|
||||
|
||||
60
src/dsp/filter/iir/DeemphasisFilter.java
Normal file
60
src/dsp/filter/iir/DeemphasisFilter.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
65
src/dsp/gain/NonClippingGain.java
Normal file
65
src/dsp/gain/NonClippingGain.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 ) );
|
||||
|
||||
@@ -27,8 +27,6 @@ public class AMDecoder extends PrimaryDecoder
|
||||
public AMDecoder( DecodeConfiguration config )
|
||||
{
|
||||
super( config );
|
||||
|
||||
//TODO: wire in the am decoder pieces
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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" );
|
||||
}
|
||||
|
||||
@@ -260,9 +260,6 @@ public class C4FMSymbolFilter implements FrequencyCorrectionResetListener,
|
||||
// .getErrorCorrection() );
|
||||
|
||||
mFrequencyAdjustmentRequested = 0;
|
||||
|
||||
/* reset internal frequency error tracker */
|
||||
mCoarseFrequencyCorrection = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ) );
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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() ) );
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -118,7 +118,7 @@ public class AirspyTuner extends Tuner
|
||||
@Override
|
||||
public double getSampleSize()
|
||||
{
|
||||
return 12.0;
|
||||
return 13.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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 );
|
||||
|
||||
|
||||
@@ -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" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user