mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -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:
@@ -20,7 +20,6 @@ import settings.Setting;
|
||||
import settings.SettingChangeListener;
|
||||
import settings.SettingsManager;
|
||||
import alias.Alias;
|
||||
import audio.AudioEvent.Type;
|
||||
import audio.metadata.AudioMetadata;
|
||||
import audio.metadata.Metadata;
|
||||
import audio.metadata.MetadataType;
|
||||
|
||||
@@ -1,6 +1,25 @@
|
||||
package audio;
|
||||
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* 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
|
||||
* 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 java.util.ArrayList;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
|
||||
@@ -18,7 +18,6 @@ import audio.squelch.SquelchState;
|
||||
import controller.channel.ChannelEvent;
|
||||
import controller.channel.IChannelEventListener;
|
||||
import dsp.filter.Filters;
|
||||
import dsp.filter.dc.AveragingDCRemovalFilter_RB;
|
||||
import dsp.filter.dc.DCRemovalFilter_RB;
|
||||
import dsp.filter.fir.real.RealFIRFilter_R_R;
|
||||
|
||||
@@ -73,7 +72,7 @@ public class AudioModule extends Module implements IAudioPacketProvider,
|
||||
|
||||
if( mRemoveDC )
|
||||
{
|
||||
mDCRemovalFilter = new AveragingDCRemovalFilter_RB( DC_REMOVAL_RATIO );
|
||||
mDCRemovalFilter = new DCRemovalFilter_RB( DC_REMOVAL_RATIO );
|
||||
mDCRemovalFilter.setListener( this );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
******************************************************************************/
|
||||
package audio.inverted;
|
||||
|
||||
import util.Oscillator;
|
||||
import dsp.filter.FilterFactory;
|
||||
import dsp.filter.FloatFIRFilter;
|
||||
import dsp.filter.Window.WindowType;
|
||||
import dsp.mixer.Oscillator;
|
||||
|
||||
/**
|
||||
* Based on the AudioType applied, produces normal, muted, or audio inverted
|
||||
|
||||
@@ -19,9 +19,9 @@ package audio.inverted;
|
||||
|
||||
import audio.InversionFrequency;
|
||||
import sample.real.RealSampleListener;
|
||||
import util.Oscillator;
|
||||
import dsp.filter.Filters;
|
||||
import dsp.filter.FloatFIRFilter;
|
||||
import dsp.mixer.Oscillator;
|
||||
|
||||
/**
|
||||
* Applies audio inversion (or un-inversion) to a stream of float audio samples
|
||||
|
||||
@@ -4,7 +4,6 @@ import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.sound.sampled.BooleanControl;
|
||||
import javax.sound.sampled.FloatControl;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
@@ -10,7 +10,6 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.sound.sampled.BooleanControl;
|
||||
import javax.sound.sampled.Control;
|
||||
import javax.sound.sampled.FloatControl;
|
||||
import javax.sound.sampled.LineUnavailableException;
|
||||
import javax.sound.sampled.Mixer;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -31,7 +31,7 @@ import dsp.filter.Window.WindowType;
|
||||
import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
|
||||
import dsp.filter.halfband.complex.HalfBandFilter_CB_CB;
|
||||
|
||||
public class ComplexPrimeCICDecimate
|
||||
public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
|
||||
{
|
||||
public static int[] PRIMES = { 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,
|
||||
59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,
|
||||
@@ -163,6 +163,7 @@ public class ComplexPrimeCICDecimate
|
||||
/**
|
||||
* Primary input method for receiving sample arrays composed as I,Q,I,Q, etc.
|
||||
*/
|
||||
@Override
|
||||
public void receive( ComplexBuffer buffer )
|
||||
{
|
||||
if( mFirstDecimatingStage != null )
|
||||
@@ -343,9 +344,8 @@ public class ComplexPrimeCICDecimate
|
||||
|
||||
|
||||
/**
|
||||
* Output adapter - applies gain correction, applies cleanup filter, casts
|
||||
* double-valued samples into float-valued complex sample and sends the
|
||||
* result to the registered listener.
|
||||
* Output adapter - applies gain correction and cleanup filter and broadcast
|
||||
* to registered listener.
|
||||
*/
|
||||
public class Output implements ComplexSampleListener
|
||||
{
|
||||
|
||||
@@ -17,18 +17,16 @@
|
||||
******************************************************************************/
|
||||
package dsp.filter.dc;
|
||||
|
||||
public abstract class DCRemovalFilter
|
||||
public class DCRemovalFilter
|
||||
{
|
||||
private float mAverage;
|
||||
private float mRatio;
|
||||
protected float mAverage;
|
||||
protected float mRatio;
|
||||
|
||||
public DCRemovalFilter( float ratio )
|
||||
{
|
||||
mRatio = ratio;
|
||||
}
|
||||
|
||||
public abstract void dispose();
|
||||
|
||||
public void reset()
|
||||
{
|
||||
mAverage = 0.0f;
|
||||
@@ -36,8 +34,8 @@ public abstract class DCRemovalFilter
|
||||
|
||||
public float filter( float sample )
|
||||
{
|
||||
mAverage += mRatio * ( sample - mAverage );
|
||||
|
||||
return sample - mAverage;
|
||||
float filtered = sample - mAverage;
|
||||
mAverage += mRatio * filtered;
|
||||
return filtered;
|
||||
}
|
||||
}
|
||||
@@ -18,17 +18,18 @@
|
||||
package dsp.filter.dc;
|
||||
|
||||
import sample.Listener;
|
||||
import sample.real.RealBuffer;
|
||||
import sample.complex.ComplexBuffer;
|
||||
|
||||
public class AveragingDCRemovalFilter_RB extends DCRemovalFilter_RB
|
||||
implements Listener<RealBuffer>
|
||||
public class DCRemovalFilter_CB implements Listener<ComplexBuffer>
|
||||
{
|
||||
private float mAverage;
|
||||
private float mRatio;
|
||||
|
||||
public AveragingDCRemovalFilter_RB( float ratio )
|
||||
private Listener<ComplexBuffer> mListener;
|
||||
private DCRemovalFilter mIFilter;
|
||||
private DCRemovalFilter mQFilter;
|
||||
|
||||
public DCRemovalFilter_CB( float ratio )
|
||||
{
|
||||
mRatio = ratio;
|
||||
mIFilter = new DCRemovalFilter( ratio );
|
||||
mQFilter = new DCRemovalFilter( ratio );
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
@@ -36,31 +37,35 @@ public class AveragingDCRemovalFilter_RB extends DCRemovalFilter_RB
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
public void reset()
|
||||
public float[] filter( float[] samples )
|
||||
{
|
||||
mAverage = 0.0f;
|
||||
}
|
||||
for( int x = 0; x < samples.length; x += 2 )
|
||||
{
|
||||
samples[ x ] = mIFilter.filter( samples[ x ] );
|
||||
samples[ x + 1 ] = mQFilter.filter( samples[ x + 1 ] );
|
||||
}
|
||||
|
||||
public float filter( float sample )
|
||||
{
|
||||
mAverage += mRatio * ( sample - mAverage );
|
||||
return samples;
|
||||
}
|
||||
|
||||
return sample - mAverage;
|
||||
public ComplexBuffer filter( ComplexBuffer buffer )
|
||||
{
|
||||
filter( buffer.getSamples() );
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void receive( RealBuffer buffer )
|
||||
public void receive( ComplexBuffer buffer )
|
||||
{
|
||||
if( mListener != null )
|
||||
{
|
||||
float[] samples = buffer.getSamples();
|
||||
|
||||
for( int x = 0; x < samples.length; x++ )
|
||||
{
|
||||
samples[ x ] = filter( samples[ x ] );
|
||||
}
|
||||
|
||||
mListener.receive( buffer );
|
||||
mListener.receive( filter( buffer ) );
|
||||
}
|
||||
}
|
||||
|
||||
public void setListener( Listener<ComplexBuffer> listener )
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,14 @@ package dsp.filter.dc;
|
||||
import sample.Listener;
|
||||
import sample.real.RealBuffer;
|
||||
|
||||
public abstract class DCRemovalFilter_RB implements Listener<RealBuffer>
|
||||
public class DCRemovalFilter_RB extends DCRemovalFilter
|
||||
implements Listener<RealBuffer>
|
||||
{
|
||||
protected Listener<RealBuffer> mListener;
|
||||
|
||||
public DCRemovalFilter_RB()
|
||||
public DCRemovalFilter_RB( float ratio )
|
||||
{
|
||||
|
||||
super( ratio );
|
||||
}
|
||||
|
||||
public void setListener( Listener<RealBuffer> listener )
|
||||
@@ -27,5 +28,34 @@ public abstract class DCRemovalFilter_RB implements Listener<RealBuffer>
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
public abstract void reset();
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( RealBuffer buffer )
|
||||
{
|
||||
if( mListener != null )
|
||||
{
|
||||
mListener.receive( filter( buffer ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ public class DCRemovalFilter_R_R extends DCRemovalFilter implements RealSampleLi
|
||||
super( ratio );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
mListener = null;
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package dsp.filter.dc;
|
||||
|
||||
import sample.Listener;
|
||||
import sample.real.RealBuffer;
|
||||
|
||||
public class IIRSinglePoleDCRemovalFilter_RB extends DCRemovalFilter_RB
|
||||
public class IIRSinglePoleDCRemovalFilter_RB implements Listener<RealBuffer>
|
||||
{
|
||||
private float mAlpha;
|
||||
private float mPreviousInput = 0.0f;
|
||||
private float mPreviousOutput = 0.0f;
|
||||
private Listener<RealBuffer> mListener;
|
||||
|
||||
/**
|
||||
* IIR single-pole DC removal filter, as described by J M de Freitas in
|
||||
@@ -24,6 +26,14 @@ public class IIRSinglePoleDCRemovalFilter_RB extends DCRemovalFilter_RB
|
||||
|
||||
@Override
|
||||
public void receive( RealBuffer buffer )
|
||||
{
|
||||
if( mListener != null )
|
||||
{
|
||||
mListener.receive( filter( buffer ) );
|
||||
}
|
||||
}
|
||||
|
||||
public RealBuffer filter( RealBuffer buffer )
|
||||
{
|
||||
float[] samples = buffer.getSamples();
|
||||
|
||||
@@ -31,11 +41,8 @@ public class IIRSinglePoleDCRemovalFilter_RB extends DCRemovalFilter_RB
|
||||
{
|
||||
samples[ x ] = filter( samples[ x ] );
|
||||
}
|
||||
|
||||
if( mListener != null )
|
||||
{
|
||||
mListener.receive( buffer );
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public float filter( float sample )
|
||||
@@ -49,10 +56,14 @@ public class IIRSinglePoleDCRemovalFilter_RB extends DCRemovalFilter_RB
|
||||
return currentOutput;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
mPreviousInput = 0.0f;
|
||||
mPreviousOutput = 0.0f;
|
||||
}
|
||||
|
||||
public void setListener( Listener<RealBuffer> listener )
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,21 @@ public class HalfBandFilter_RB_RB extends HalfBandFilter implements Listener<Rea
|
||||
private Listener<RealBuffer> mListener;
|
||||
|
||||
/**
|
||||
* Halfband filter for real buffers with optional decimation by two.
|
||||
*
|
||||
* @param coefficients
|
||||
* @param gain
|
||||
* @param decimate
|
||||
* If this filter is used in non-decimating mode, the filter will overwrite
|
||||
* the inbound sample buffer with the filtered samples. This will cause
|
||||
* issues if the buffers are being used across several processes are sharing
|
||||
* the buffer, since this filter will modify the contents of the object, so
|
||||
* consider making a copy of the buffer prior to feeding the buffer to this
|
||||
* filter.
|
||||
*
|
||||
* If used in decimating mode, a new (half-length) buffer will be created
|
||||
* for the filtered samples.
|
||||
*
|
||||
* @param coefficients - filter kernel
|
||||
* @param gain - gain to apply to the outputs
|
||||
* @param decimate - true for decimate by 2, or false for no decimation
|
||||
*/
|
||||
public HalfBandFilter_RB_RB( float[] coefficients, float gain, boolean decimate )
|
||||
{
|
||||
@@ -28,12 +39,18 @@ public class HalfBandFilter_RB_RB extends HalfBandFilter implements Listener<Rea
|
||||
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void receive( RealBuffer buffer )
|
||||
{
|
||||
if( mListener != null )
|
||||
{
|
||||
mListener.receive( filter( buffer ) );
|
||||
}
|
||||
}
|
||||
|
||||
public RealBuffer filter( RealBuffer buffer )
|
||||
{
|
||||
if( mDecimate )
|
||||
{
|
||||
float[] samples = buffer.getSamples();
|
||||
@@ -72,7 +89,7 @@ public class HalfBandFilter_RB_RB extends HalfBandFilter implements Listener<Rea
|
||||
mDecimateFlag = !mDecimateFlag;
|
||||
}
|
||||
|
||||
mListener.receive( new RealBuffer( decimated ) );
|
||||
return new RealBuffer( decimated );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -84,9 +101,8 @@ public class HalfBandFilter_RB_RB extends HalfBandFilter implements Listener<Rea
|
||||
samples[ x ] = filter( samples[ x ] );
|
||||
}
|
||||
|
||||
mListener.receive( buffer );
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setListener( Listener<RealBuffer> listener )
|
||||
|
||||
281
src/dsp/filter/hilbert/HilbertTransform.java
Normal file
281
src/dsp/filter/hilbert/HilbertTransform.java
Normal file
@@ -0,0 +1,281 @@
|
||||
package dsp.filter.hilbert;
|
||||
|
||||
/*******************************************************************************
|
||||
* 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 java.util.Arrays;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.Listener;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import sample.real.RealBuffer;
|
||||
import dsp.filter.Filters;
|
||||
|
||||
public class HilbertTransform implements Listener<RealBuffer>
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger( HilbertTransform.class );
|
||||
|
||||
private float[] mHilbertFilter;
|
||||
|
||||
private Listener<ComplexBuffer> mListener;
|
||||
|
||||
private float[] mBuffer;
|
||||
private int mBufferSize;
|
||||
private int mBufferPointer;
|
||||
|
||||
private int[][] mIndexMap;
|
||||
private int mMapHeight;
|
||||
private int mCenterTapIndex;
|
||||
private float mCenterCoefficent;
|
||||
|
||||
private boolean mInvertFlag = false;
|
||||
|
||||
/**
|
||||
* Hilbert transform filter used for converting real-valued samples into
|
||||
* complex valued samples using frequency translation (FS/4) and a half-band
|
||||
* filter.
|
||||
*
|
||||
* This filter uses a circular sample buffer and a pre-calculated
|
||||
* index map to correctly map the filter coefficients to the circular delay
|
||||
* buffer contents as each new sample is added to the buffer and processed.
|
||||
*
|
||||
* Half-band filter coefficients used in this filter must be of length N
|
||||
* where (N + 1) is a multiple of 4. This filter uses a pre-defined half
|
||||
* band filter with 47 coefficients.
|
||||
*
|
||||
* This transform process is described in Understanding Digital Signal
|
||||
* Processing, Lyons, 3e, 2011, sections 13.1.2 and 13.1.3 (p 674-678) and
|
||||
* implemented as described in Section 13.37.1 and 13.37.2 (p 802-804)
|
||||
*/
|
||||
public HilbertTransform()
|
||||
{
|
||||
convertHalfBandToHilbert( Filters.HALF_BAND_FILTER_47T.getCoefficients() );
|
||||
|
||||
mBufferSize = mHilbertFilter.length + 1;
|
||||
mBuffer = new float[ mBufferSize ];
|
||||
|
||||
mCenterCoefficent = mHilbertFilter[ mHilbertFilter.length / 2 ];
|
||||
|
||||
generateIndexMap( mHilbertFilter.length );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( RealBuffer buffer )
|
||||
{
|
||||
if( mListener != null )
|
||||
{
|
||||
mListener.receive( filter( buffer ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the listener to receive filtered complex buffers.
|
||||
*/
|
||||
public void setListener( Listener<ComplexBuffer> listener )
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies Hilbert transform to the real sample buffer and returns a
|
||||
* complex sample buffer
|
||||
*/
|
||||
public ComplexBuffer filter( RealBuffer buffer )
|
||||
{
|
||||
return new ComplexBuffer( filter( buffer.getSamples() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the continuous real-sample stream by treating the stream as a
|
||||
* set of interleaved values and applying two hilbert filters against each
|
||||
* interleaved set (I & Q). Frequency translation has already been
|
||||
* pre-applied to the hilbert filter coefficients.
|
||||
*
|
||||
* Filtering of the inphase component is a simple multiply by the center
|
||||
* coefficient (0.5) since all of the other coefficients are zero-valued.
|
||||
*
|
||||
* Filtering of the quadrature component uses a folded FIR structure and
|
||||
* expects the hilbert coefficients to be symmetric with all negative valued
|
||||
* coefficients below the center coefficient and all positive-valued coefficients
|
||||
* above the center coefficient. So, convolution only applies the filter
|
||||
* coefficient against the difference of the two samples.
|
||||
*
|
||||
* ( coefficient * value1 ) + (-coefficient * value2 ) = coefficient * ( value1 - value2 )
|
||||
*
|
||||
* Performs frequency translation by FS/2 on the final filtered values by
|
||||
* applying a sequence of 1,-1 (sign change) to each I/Q output sample.
|
||||
*/
|
||||
public float[] filter( float[] samples )
|
||||
{
|
||||
float accumulator;
|
||||
|
||||
for( int y = 0; y < samples.length; y +=2 )
|
||||
{
|
||||
insert( samples[ y ] );
|
||||
insert( samples[ y + 1 ] );
|
||||
|
||||
accumulator = 0.0f;
|
||||
|
||||
int index = mBufferPointer / 2;
|
||||
|
||||
for( int x = 0; x < mHilbertFilter.length / 2; x += 2 )
|
||||
{
|
||||
accumulator += mHilbertFilter[ x ] *
|
||||
( mBuffer[ mIndexMap[ index ][ x + 1] ] -
|
||||
mBuffer[ mIndexMap[ index ][ x ] ] );
|
||||
}
|
||||
|
||||
//Perform FS/2 frequency translation on the final filtered values
|
||||
if( mInvertFlag )
|
||||
{
|
||||
//inphase
|
||||
samples[ y ] = -( mCenterCoefficent *
|
||||
mBuffer[ mIndexMap[ index ][ mCenterTapIndex ] ] );
|
||||
|
||||
//quadrature
|
||||
samples[ y + 1 ] = -accumulator;
|
||||
}
|
||||
else
|
||||
{
|
||||
//inphase
|
||||
samples[ y ] = mCenterCoefficent *
|
||||
mBuffer[ mIndexMap[ index ][ mCenterTapIndex ] ];
|
||||
|
||||
//quadrature
|
||||
samples[ y + 1 ] = accumulator;
|
||||
}
|
||||
|
||||
mInvertFlag = !mInvertFlag;
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the sample into the circular buffer, overwriting the oldest value
|
||||
*/
|
||||
private void insert( float sample )
|
||||
{
|
||||
mBuffer[ mBufferPointer++ ] = sample;
|
||||
|
||||
mBufferPointer = mBufferPointer % mBufferSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an index map to support efficient lookup of sample indexes from
|
||||
* the circular buffer.
|
||||
*
|
||||
* The first index of the map corresponds to the current buffer pointer
|
||||
* setting.
|
||||
*
|
||||
* The second index is structured in sample pairs to align with
|
||||
* the hilbert filter coefficients. For example, the first iteration of
|
||||
* the filter would use filter coefficient 0 and the index map values 0 and
|
||||
* 1 would be pointing to samples 46 and 0.
|
||||
*
|
||||
* The final element of each index set is the index of the center sample.
|
||||
*/
|
||||
private void generateIndexMap( int size )
|
||||
{
|
||||
mMapHeight = size / 2 + 1;
|
||||
int mapWidth = mMapHeight + 1;
|
||||
|
||||
mIndexMap = new int[ mMapHeight ][ mapWidth ];
|
||||
|
||||
//Setup the first row with buffer pointer index 0 as a starting point
|
||||
for( int x = 0; x < mapWidth - 1; x += 2 )
|
||||
{
|
||||
mIndexMap[ 0 ][ x ] = size - 1 - x;
|
||||
mIndexMap[ 0 ][ x + 1 ] = x;
|
||||
}
|
||||
|
||||
//Place the center index at the end of the array
|
||||
mCenterTapIndex = mapWidth - 1;
|
||||
|
||||
mIndexMap[ 0 ][ mCenterTapIndex ] = size / 2;
|
||||
|
||||
//For each subsequent row, increment the previous row's value by 2,
|
||||
//wrapping as needed, to keep the values between 0 and size - 1
|
||||
for( int x = 1; x < mMapHeight; x++ )
|
||||
{
|
||||
for( int y = 0; y < mapWidth; y++ )
|
||||
{
|
||||
mIndexMap[ x ][ y ] = mIndexMap[ x - 1 ][ y ] + 2;
|
||||
|
||||
if( mIndexMap[ x ][ y ] >= size )
|
||||
{
|
||||
mIndexMap[ x ][ y ] -= size + 1;
|
||||
|
||||
//Special handling for center tap wrap around
|
||||
if( y == mCenterTapIndex && mIndexMap[ x ][ y ] < 0 )
|
||||
{
|
||||
mIndexMap[ x ][ y ] = size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the index map contents
|
||||
*/
|
||||
public void logIndexMap()
|
||||
{
|
||||
for( int[] indexSet: mIndexMap )
|
||||
{
|
||||
mLog.debug( "Row:" + Arrays.toString( indexSet ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the half-band filter coefficients for use as hilbert transform
|
||||
* filter coefficients. Sets all even-numbered coefficients left of center
|
||||
* to negative and all even-numbered coefficients right of center to positive
|
||||
* which applies a FS/4 frequency translation to the coefficients.
|
||||
*
|
||||
* Note: even though the coefficients above the center coefficient are being
|
||||
* defined, they are unused during the convolution process since we're using
|
||||
* a folded FIR structure to exploit the symmetric nature of the half-band
|
||||
* filter coefficients. The polarity difference between the upper and lower
|
||||
* halves is accounted for in the filter() method.
|
||||
*/
|
||||
private void convertHalfBandToHilbert( float[] coefficients )
|
||||
{
|
||||
mHilbertFilter = new float[ coefficients.length ];
|
||||
|
||||
int middle = coefficients.length / 2;
|
||||
|
||||
for( int x = 0; x < coefficients.length; x++ )
|
||||
{
|
||||
if( x < middle )
|
||||
{
|
||||
mHilbertFilter[ x ] = -Math.abs( coefficients[ x ] );
|
||||
}
|
||||
else if( x > middle )
|
||||
{
|
||||
mHilbertFilter[ x ] = Math.abs( coefficients[ x ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
mHilbertFilter[ x ] = coefficients[ x ];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
281
src/dsp/filter/hilbert/InterpolatingHilbertTransform.java
Normal file
281
src/dsp/filter/hilbert/InterpolatingHilbertTransform.java
Normal file
@@ -0,0 +1,281 @@
|
||||
package dsp.filter.hilbert;
|
||||
|
||||
/*******************************************************************************
|
||||
* 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/>
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
* Half-band filter coefficients retrieved October 2015 from:
|
||||
* https://github.com/airspy/host/libairspy/src/filters.h
|
||||
*
|
||||
* Copyright (C) 2014, Youssef Touil <youssef@airspy.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
******************************************************************************/
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.Listener;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import sample.real.RealBuffer;
|
||||
|
||||
public class InterpolatingHilbertTransform implements Listener<RealBuffer>
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger( InterpolatingHilbertTransform.class );
|
||||
|
||||
private static final float[] HALF_BAND_FILTER =
|
||||
{
|
||||
-0.000998606272947510f, 0.0f, 0.001695637278417295f, 0.0f,
|
||||
-0.003054430179754289f, 0.0f, 0.005055504379767936f, 0.0f,
|
||||
-0.007901319195893647f, 0.0f, 0.011873357051047719f, 0.0f,
|
||||
-0.017411159379930066f, 0.0f, 0.025304817427568772f, 0.0f,
|
||||
-0.037225225204559217f, 0.0f, 0.057533286997004301f, 0.0f,
|
||||
-0.102327462004259350f, 0.0f, 0.317034472508947400f, 0.5f,
|
||||
0.317034472508947400f, 0.0f, -0.102327462004259350f, 0.0f,
|
||||
0.057533286997004301f, 0.0f, -0.037225225204559217f, 0.0f,
|
||||
0.025304817427568772f, 0.0f, -0.017411159379930066f, 0.0f,
|
||||
0.011873357051047719f, 0.0f, -0.007901319195893647f, 0.0f,
|
||||
0.005055504379767936f, 0.0f, -0.003054430179754289f, 0.0f,
|
||||
0.001695637278417295f, 0.0f, -0.000998606272947510f
|
||||
};
|
||||
|
||||
private static final float GAIN = 2.0f;
|
||||
|
||||
private Listener<ComplexBuffer> mListener;
|
||||
|
||||
private float[] mBuffer;
|
||||
private int mBufferSize;
|
||||
private int mBufferPointer;
|
||||
private int mGroupDelayedInphaseTapPointIndex;
|
||||
|
||||
private float[] mCoefficients;
|
||||
private int[][] mIndexMap;
|
||||
|
||||
|
||||
/**
|
||||
* Hilbert transform filter used for converting real-valued samples into
|
||||
* complex valued samples using frequency translation (FS/4) and a half-band
|
||||
* filter.
|
||||
*
|
||||
* This filter uses a circular sample buffer and a pre-calculated
|
||||
* index map to correctly map the filter coefficients to the circular delay
|
||||
* buffer contents as each new sample is added to the buffer and processed.
|
||||
*
|
||||
* Half-band filter coefficients used in this filter must be of length N
|
||||
* where (N + 1) is a multiple of 4. This filter uses a pre-defined half
|
||||
* band filter with 47 coefficients.
|
||||
*
|
||||
* This transform process is described in Understanding Digital Signal
|
||||
* Processing, Lyons, 3e, 2011, sections 13.1.2 and 13.1.3 (p 674-678) and
|
||||
* implemented as described in Section 13.37.1 and 13.37.2 (p 802-804)
|
||||
*/
|
||||
public InterpolatingHilbertTransform()
|
||||
{
|
||||
mBufferSize = HALF_BAND_FILTER.length;
|
||||
mBuffer = new float[ mBufferSize ];
|
||||
|
||||
createHilbertCoefficients();
|
||||
|
||||
generateIndexMap( mBufferSize );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( RealBuffer buffer )
|
||||
{
|
||||
if( mListener != null )
|
||||
{
|
||||
mListener.receive( filter( buffer ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the listener to receive filtered complex buffers.
|
||||
*/
|
||||
public void setListener( Listener<ComplexBuffer> listener )
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies Hilbert transform to the real sample buffer and returns a
|
||||
* complex sample buffer
|
||||
*/
|
||||
public ComplexBuffer filter( RealBuffer buffer )
|
||||
{
|
||||
return new ComplexBuffer( filter( buffer.getSamples() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a hilbert transform to an array of real samples and returns an
|
||||
* array twice as long containing I,Q,I,Q ... complex samples
|
||||
*/
|
||||
public float[] filter( float[] samples )
|
||||
{
|
||||
float[] filtered = new float[ samples.length * 2 ];
|
||||
|
||||
float accumulator;
|
||||
|
||||
for( int y = 0; y < samples.length; y++ )
|
||||
{
|
||||
insert( samples[ y ] );
|
||||
|
||||
accumulator = 0.0f;
|
||||
|
||||
for( int x = 0; x < mCoefficients.length; x++ )
|
||||
{
|
||||
accumulator += mCoefficients[ x ] *
|
||||
mBuffer[ mIndexMap[ mBufferPointer ][ x ] ] ;
|
||||
}
|
||||
|
||||
int index = y * 2;
|
||||
|
||||
/* We use the buffered sample at the half group delay point as the
|
||||
* Inphase value and the accumulator as the quadrature value */
|
||||
filtered[ index ] = mBuffer[ mIndexMap[ mBufferPointer ]
|
||||
[ mGroupDelayedInphaseTapPointIndex ] ];
|
||||
|
||||
filtered[ index + 1 ] = accumulator;
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the sample into the circular buffer, overwriting the oldest value
|
||||
*/
|
||||
private void insert( float sample )
|
||||
{
|
||||
mBuffer[ mBufferPointer ] = sample;
|
||||
|
||||
mBufferPointer++;
|
||||
|
||||
mBufferPointer = mBufferPointer % mBufferSize;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates an N x (N + 1 / 2) index map enabling quick access to the
|
||||
* circular buffer samples to support convolution.
|
||||
*
|
||||
* As the buffer pointer shifts right, as each sample is added, we use the
|
||||
* index map pointed to by the buffer pointer, so that the filter coefficients
|
||||
* are pointing to the correct circular buffer samples.
|
||||
*
|
||||
* We add an extra index to each index map with a value that points to the
|
||||
* current index of the middle sample in the buffer, so that we can retrieve
|
||||
* that value as the half-group delayed sample that forms the inphase
|
||||
* component.
|
||||
*/
|
||||
private void generateIndexMap( int size )
|
||||
{
|
||||
int mapWidth = ( ( size + 1 ) / 2 ) + 1;
|
||||
|
||||
int center = ( size - 1 ) / 2;
|
||||
|
||||
//Set center tap index for coefficients array
|
||||
mGroupDelayedInphaseTapPointIndex = center + 1;
|
||||
|
||||
mIndexMap = new int[ size ][ mapWidth ];
|
||||
|
||||
//Setup the first row with buffer pointer index 0 as a starting point
|
||||
for( int x = 0; x < mapWidth - 1; x++ )
|
||||
{
|
||||
mIndexMap[ 0 ][ x ] = size - 1 - ( x * 2 );
|
||||
}
|
||||
|
||||
//Place the group delayed in-phase sample tap point in last element of
|
||||
//the first row
|
||||
mIndexMap[ 0 ][ mGroupDelayedInphaseTapPointIndex ] = center;
|
||||
|
||||
//For each subsequent row, increment the previous row's value by 1,
|
||||
//subtracting size as needed, to keep the values between 0 and size - 1
|
||||
for( int x = 1; x < size; x++ )
|
||||
{
|
||||
for( int y = 0; y < mapWidth; y++ )
|
||||
{
|
||||
mIndexMap[ x ][ y ] = mIndexMap[ x - 1 ][ y ] + 1;
|
||||
|
||||
if( mIndexMap[ x ][ y ] >= size )
|
||||
{
|
||||
mIndexMap[ x ][ y ] -= size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void logIndexMap()
|
||||
{
|
||||
for( int[] indexSet: mIndexMap )
|
||||
{
|
||||
mLog.debug( "Row:" + Arrays.toString( indexSet ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the half-band filter coefficients into a packed set of hilbert
|
||||
* coefficients by removing all of the odd index, zero-valued coefficients
|
||||
* and the center 0.5 coefficient. Then, all coefficients left of the
|
||||
* original center are made negative and all coefficients right of the
|
||||
* original center are made positive. These coefficient sign modifications
|
||||
* effectively incorporate a frequency translation by FS/4 into the hilbert
|
||||
* coefficients. A gain of 2.0 is applied to the coefficients to offset the
|
||||
* gain of 0.5 induced by the filter.
|
||||
*/
|
||||
private void createHilbertCoefficients()
|
||||
{
|
||||
int half = ( HALF_BAND_FILTER.length + 1 ) / 2;
|
||||
int middle = half / 2;
|
||||
|
||||
mCoefficients = new float[ half ];
|
||||
|
||||
/* Pack all of the even numbered HB coefficients into our coefficient
|
||||
* array and pre-apply a gain of two to offset the loss in the filter */
|
||||
for( int x = 0; x < half; x++ )
|
||||
{
|
||||
mCoefficients[ x ] = HALF_BAND_FILTER[ x * 2 ] * GAIN;
|
||||
|
||||
/* Make all of the left of center coefficients negative */
|
||||
if( x < middle && mCoefficients[ x ] > 0.0f )
|
||||
{
|
||||
mCoefficients[ x ] = -mCoefficients[ x ];
|
||||
}
|
||||
/* Make all of the right of center coefficients positive */
|
||||
else if( x >= middle && mCoefficients[ x ] < 0.0f )
|
||||
{
|
||||
mCoefficients[ x ] = -mCoefficients[ x ];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
39
src/dsp/filter/smoothing/GaussianSmoothingFilter.java
Normal file
39
src/dsp/filter/smoothing/GaussianSmoothingFilter.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package dsp.filter.smoothing;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class GaussianSmoothingFilter extends SmoothingFilter
|
||||
{
|
||||
private static Map<Integer,float[]> mMap = new HashMap<>();
|
||||
|
||||
static
|
||||
{
|
||||
mMap.put( 3, new float[] { 0.1842153217f,0.6315693566f,0.1842153217f } );
|
||||
mMap.put( 5, new float[] { 0.0646709835f,0.2446878423f,0.3812823483f,0.2446878423f,0.0646709835f } );
|
||||
mMap.put( 7, new float[] { 0.0355966134f,0.1103628062f,0.2176061504f,0.27286886f,0.2176061504f,0.1103628062f,0.0355966134f } );
|
||||
mMap.put( 9, new float[] { 0.0237625774f,0.061955345f,0.1228439993f,0.185233293f,0.2124095706f,0.185233293f,0.1228439993f,0.061955345f,0.0237625774f } );
|
||||
mMap.put( 11, new float[] { 0.0175873706f,0.040124091f,0.0762094006f,0.120506627f,0.1586398759f,0.1738652697f,0.1586398759f,0.120506627f,0.0762094006f,0.040124091f,0.0175873706f } );
|
||||
mMap.put( 13, new float[] { 0.0138646514f,0.0285342951f,0.0515030495f,0.0815279159f,0.1131845993f,0.137808478f,0.1471540217f,0.137808478f,0.1131845993f,0.0815279159f,0.0515030495f,0.0285342951f,0.0138646514f } );
|
||||
mMap.put( 15, new float[] { 0.011400054f,0.0216349783f,0.0372047506f,0.0579739252f,0.0818576722f,0.1047317717f,0.1214199044f,0.1275538873f,0.1214199044f,0.1047317717f,0.0818576722f,0.0579739252f,0.0372047506f,0.0216349783f,0.011400054f } );
|
||||
mMap.put( 17, new float[] { 0.0096581767f,0.0171732764f,0.0282802625f,0.0431306476f,0.0609201251f,0.07969074f,0.0965444243f,0.1083225312f,0.1125596326f,0.1083225312f,0.0965444243f,0.07969074f,0.0609201251f,0.0431306476f,0.0282802625f,0.0171732764f,0.0096581767f } );
|
||||
mMap.put( 19, new float[] { 0.0083665107f,0.0141035977f,0.0223580953f,0.0333318211f,0.0467307368f,0.0616120364f,0.0763919939f,0.0890736966f,0.097672084f,0.1007188551f,0.097672084f,0.0890736966f,0.0763919939f,0.0616120364f,0.0467307368f,0.0333318211f,0.0223580953f,0.0141035977f,0.0083665107f } );
|
||||
mMap.put( 21, new float[] { 0.0073728906f,0.0118884085f,0.0182292596f,0.0265811467f,0.0368585098f,0.0486027908f,0.0609458331f,0.0726751947f,0.0824114898f,0.0888686744f,0.091131604f,0.0888686744f,0.0824114898f,0.0726751947f,0.0609458331f,0.0486027908f,0.0368585098f,0.0265811467f,0.0182292596f,0.0118884085f,0.0073728906f } );
|
||||
mMap.put( 23, new float[] { 0.0065861356f,0.010228416f,0.0152327635f,0.0217541246f,0.0297918261f,0.0391241787f,0.0492703835f,0.0595003129f,0.0689041144f,0.0765180086f,0.0814844543f,0.0832105633f,0.0814844543f,0.0765180086f,0.0689041144f,0.0595003129f,0.0492703835f,0.0391241787f,0.0297918261f,0.0217541246f,0.0152327635f,0.010228416f,0.0065861356f } );
|
||||
mMap.put( 25, new float[] { 0.0059484873f,0.0089460248f,0.0129850288f,0.0181905149f,0.0245944051f,0.0320934871f,0.0404191102f,0.0491298959f,0.0576360481f,0.0652577058f,0.0713113508f,0.07520986f,0.076556162f,0.07520986f,0.0713113508f,0.0652577058f,0.0576360481f,0.0491298959f,0.0404191102f,0.0320934871f,0.0245944051f,0.0181905149f,0.0129850288f,0.0089460248f,0.0059484873f } );
|
||||
mMap.put( 27, new float[] { 0.0054216802f,0.007930248f,0.0112519401f,0.0154865908f,0.0206762609f,0.0267778655f,0.0336409056f,0.0409965368f,0.0484634619f,0.0555737199f,0.0618176187f,0.0667026128f,0.0698170011f,0.0708871156f,0.0698170011f,0.0667026128f,0.0618176187f,0.0555737199f,0.0484634619f,0.0409965368f,0.0336409056f,0.0267778655f,0.0206762609f,0.0154865908f,0.0112519401f,0.007930248f,0.0054216802f } );
|
||||
mMap.put( 29, new float[] { 0.0049794093f,0.0071086836f,0.0098843442f,0.013386093f,0.0176566017f,0.022683377f,0.0283828243f,0.0345900198f,0.0410575774f,0.0474660547f,0.0534466215f,0.0586144488f,0.0626089499f,0.0651351564f,0.0659996766f,0.0651351564f,0.0626089499f,0.0586144488f,0.0534466215f,0.0474660547f,0.0410575774f,0.0345900198f,0.0283828243f,0.022683377f,0.0176566017f,0.013386093f,0.0098843442f,0.0071086836f,0.0049794093f } );
|
||||
mMap.put( 31, new float[] { 0.0046030224f,0.0064323659f,0.0087836656f,0.0117208269f,0.0152833344f,0.0194740097f,0.0242476721f,0.0295027244f,0.0350777394f,0.0407547727f,0.0462703471f,0.0513339215f,0.0556523529f,0.0589576307f,0.0610342929f,0.0617426429f,0.0610342929f,0.0589576307f,0.0556523529f,0.0513339215f,0.0462703471f,0.0407547727f,0.0350777394f,0.0295027244f,0.0242476721f,0.0194740097f,0.0152833344f,0.0117208269f,0.0087836656f,0.0064323659f,0.0046030224f } );
|
||||
}
|
||||
|
||||
public GaussianSmoothingFilter()
|
||||
{
|
||||
super( mMap, 3 );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SmoothingType getSmoothingType()
|
||||
{
|
||||
return SmoothingType.GAUSSIAN;
|
||||
}
|
||||
}
|
||||
31
src/dsp/filter/smoothing/NoSmoothingFilter.java
Normal file
31
src/dsp/filter/smoothing/NoSmoothingFilter.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package dsp.filter.smoothing;
|
||||
|
||||
|
||||
public class NoSmoothingFilter extends SmoothingFilter
|
||||
{
|
||||
public NoSmoothingFilter()
|
||||
{
|
||||
super( null, 3 );
|
||||
}
|
||||
|
||||
public float[] filter( float[] data )
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
public static float[] getCoefficients( int points )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getPointSize()
|
||||
{
|
||||
return SMOOTHING_DEFAULT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SmoothingType getSmoothingType()
|
||||
{
|
||||
return SmoothingType.NONE;
|
||||
}
|
||||
}
|
||||
49
src/dsp/filter/smoothing/RectangularSmoothingFilter.java
Normal file
49
src/dsp/filter/smoothing/RectangularSmoothingFilter.java
Normal file
@@ -0,0 +1,49 @@
|
||||
package dsp.filter.smoothing;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class RectangularSmoothingFilter extends SmoothingFilter
|
||||
{
|
||||
private static Map<Integer,float[]> mMap = new HashMap<>();
|
||||
|
||||
static
|
||||
{
|
||||
mMap.put( 3, getCoefficients( 3 ) );
|
||||
mMap.put( 5, getCoefficients( 5 ) );
|
||||
mMap.put( 7, getCoefficients( 7 ) );
|
||||
mMap.put( 9, getCoefficients( 9 ) );
|
||||
mMap.put( 11, getCoefficients( 11 ) );
|
||||
mMap.put( 13, getCoefficients( 13 ) );
|
||||
mMap.put( 15, getCoefficients( 15 ) );
|
||||
mMap.put( 17, getCoefficients( 17 ) );
|
||||
mMap.put( 19, getCoefficients( 19 ) );
|
||||
mMap.put( 21, getCoefficients( 21 ) );
|
||||
mMap.put( 23, getCoefficients( 23 ) );
|
||||
mMap.put( 25, getCoefficients( 25 ) );
|
||||
mMap.put( 27, getCoefficients( 27 ) );
|
||||
mMap.put( 29, getCoefficients( 29 ) );
|
||||
mMap.put( 31, getCoefficients( 31 ) );
|
||||
}
|
||||
|
||||
public RectangularSmoothingFilter()
|
||||
{
|
||||
super( mMap, 3 );
|
||||
}
|
||||
|
||||
public static float[] getCoefficients( int points )
|
||||
{
|
||||
float[] coefficients = new float[ points ];
|
||||
|
||||
Arrays.fill( coefficients, 1.0f / (float)points );
|
||||
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SmoothingType getSmoothingType()
|
||||
{
|
||||
return SmoothingType.RECTANGLE;
|
||||
}
|
||||
}
|
||||
97
src/dsp/filter/smoothing/SmoothingFilter.java
Normal file
97
src/dsp/filter/smoothing/SmoothingFilter.java
Normal file
@@ -0,0 +1,97 @@
|
||||
package dsp.filter.smoothing;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class SmoothingFilter
|
||||
{
|
||||
public static final int SMOOTHING_MINIMUM = 3;
|
||||
public static final int SMOOTHING_MAXIMUM = 29;
|
||||
public static final int SMOOTHING_DEFAULT = 9;
|
||||
|
||||
public enum SmoothingType
|
||||
{
|
||||
NONE,
|
||||
RECTANGLE,
|
||||
TRIANGLE,
|
||||
GAUSSIAN;
|
||||
}
|
||||
|
||||
private Map<Integer,float[]> mCoefficientMap;
|
||||
private float[] mCoefficients;
|
||||
private float[] mNewCoefficients;
|
||||
private boolean mNewCoefficientsAvailable;
|
||||
|
||||
public SmoothingFilter( Map<Integer,float[]> coefficients, int index )
|
||||
{
|
||||
mCoefficientMap = coefficients;
|
||||
|
||||
if( mCoefficientMap != null )
|
||||
{
|
||||
mCoefficients = mCoefficientMap.get( index );
|
||||
}
|
||||
}
|
||||
|
||||
public abstract SmoothingType getSmoothingType();
|
||||
|
||||
public Set<Integer> getPointSizeList()
|
||||
{
|
||||
return mCoefficientMap.keySet();
|
||||
}
|
||||
|
||||
public int getPointSize()
|
||||
{
|
||||
return mCoefficients.length;
|
||||
}
|
||||
|
||||
public void setPointSize( int pointSize )
|
||||
{
|
||||
if( mCoefficientMap != null )
|
||||
{
|
||||
if( mCoefficientMap.containsKey( pointSize ) )
|
||||
{
|
||||
mNewCoefficients = mCoefficientMap.get( pointSize );
|
||||
mNewCoefficientsAvailable = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException( "Point size [" + pointSize +
|
||||
"] is not valid in the current coefficient map" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float[] filter( float[] data )
|
||||
{
|
||||
if( mNewCoefficientsAvailable )
|
||||
{
|
||||
mCoefficients = mNewCoefficients;
|
||||
mNewCoefficientsAvailable = false;
|
||||
}
|
||||
|
||||
int middle = mCoefficients.length / 2;
|
||||
|
||||
float[] filtered = new float[ data.length ];
|
||||
|
||||
int toCopy = middle;
|
||||
|
||||
System.arraycopy( data, 0, filtered, 0, toCopy );
|
||||
System.arraycopy( data, data.length - toCopy, filtered, filtered.length - toCopy, toCopy );
|
||||
|
||||
float accumulator;
|
||||
|
||||
for( int x = 0; x < data.length - mCoefficients.length + 1; x++ )
|
||||
{
|
||||
accumulator = 0.0f;
|
||||
|
||||
for( int y = 0; y < mCoefficients.length; y++ )
|
||||
{
|
||||
accumulator += data[ x + y ] * mCoefficients[ y ];
|
||||
}
|
||||
|
||||
filtered[ x + middle ] = accumulator;
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
}
|
||||
38
src/dsp/filter/smoothing/TriangularSmoothingFilter.java
Normal file
38
src/dsp/filter/smoothing/TriangularSmoothingFilter.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package dsp.filter.smoothing;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class TriangularSmoothingFilter extends SmoothingFilter
|
||||
{
|
||||
private static Map<Integer,float[]> mMap = new HashMap<>();
|
||||
|
||||
static
|
||||
{
|
||||
mMap.put( 3, new float[]{ 0.25f,0.5f,0.25f } );
|
||||
mMap.put( 5, new float[]{ 0.1111111111f,0.2222222222f,0.3333333333f,0.2222222222f,0.1111111111f } );
|
||||
mMap.put( 7, new float[]{ 0.0625f,0.125f,0.1875f,0.25f,0.1875f,0.125f,0.0625f } );
|
||||
mMap.put( 9, new float[]{ 0.04f,0.08f,0.12f,0.16f,0.2f,0.16f,0.12f,0.08f,0.04f } );
|
||||
mMap.put( 11, new float[]{ 0.0277777778f,0.0555555556f,0.0833333333f,0.1111111111f,0.1388888889f,0.1666666667f,0.1388888889f,0.1111111111f,0.0833333333f,0.0555555556f,0.0277777778f } );
|
||||
mMap.put( 13, new float[]{ 0.0204081633f,0.0408163265f,0.0612244898f,0.0816326531f,0.1020408163f,0.1224489796f,0.1428571429f,0.1224489796f,0.1020408163f,0.0816326531f,0.0612244898f,0.0408163265f,0.0204081633f } );
|
||||
mMap.put( 15, new float[]{ 0.015625f,0.03125f,0.046875f,0.0625f,0.078125f,0.09375f,0.109375f,0.125f,0.109375f,0.09375f,0.078125f,0.0625f,0.046875f,0.03125f,0.015625f } );
|
||||
mMap.put( 17, new float[]{ 0.012345679f,0.024691358f,0.037037037f,0.049382716f,0.0617283951f,0.0740740741f,0.0864197531f,0.0987654321f,0.1111111111f,0.0987654321f,0.0864197531f,0.0740740741f,0.0617283951f,0.049382716f,0.037037037f,0.024691358f,0.012345679f } );
|
||||
mMap.put( 19, new float[]{ 0.01f,0.02f,0.03f,0.04f,0.05f,0.06f,0.07f,0.08f,0.09f,0.1f,0.09f,0.08f,0.07f,0.06f,0.05f,0.04f,0.03f,0.02f,0.01f } );
|
||||
mMap.put( 21, new float[]{ 0.0082644628f,0.0165289256f,0.0247933884f,0.0330578512f,0.041322314f,0.0495867769f,0.0578512397f,0.0661157025f,0.0743801653f,0.0826446281f,0.0909090909f,0.0826446281f,0.0743801653f,0.0661157025f,0.0578512397f,0.0495867769f,0.041322314f,0.0330578512f,0.0247933884f,0.0165289256f,0.0082644628f } );
|
||||
mMap.put( 23, new float[]{ 0.0069444444f,0.0138888889f,0.0208333333f,0.0277777778f,0.0347222222f,0.0416666667f,0.0486111111f,0.0555555556f,0.0625f,0.0694444444f,0.0763888889f,0.0833333333f,0.0763888889f,0.0694444444f,0.0625f,0.0555555556f,0.0486111111f,0.0416666667f,0.0347222222f,0.0277777778f,0.0208333333f,0.0138888889f,0.0069444444f } );
|
||||
mMap.put( 25, new float[]{ 0.0059171598f,0.0118343195f,0.0177514793f,0.0236686391f,0.0295857988f,0.0355029586f,0.0414201183f,0.0473372781f,0.0532544379f,0.0591715976f,0.0650887574f,0.0710059172f,0.0769230769f,0.0710059172f,0.0650887574f,0.0591715976f,0.0532544379f,0.0473372781f,0.0414201183f,0.0355029586f,0.0295857988f,0.0236686391f,0.0177514793f,0.0118343195f,0.0059171598f } );
|
||||
mMap.put( 27, new float[]{ 0.0051020408f,0.0102040816f,0.0153061224f,0.0204081633f,0.0255102041f,0.0306122449f,0.0357142857f,0.0408163265f,0.0459183673f,0.0510204082f,0.056122449f,0.0612244898f,0.0663265306f,0.0714285714f,0.0663265306f,0.0612244898f,0.056122449f,0.0510204082f,0.0459183673f,0.0408163265f,0.0357142857f,0.0306122449f,0.0255102041f,0.0204081633f,0.0153061224f,0.0102040816f,0.0051020408f } );
|
||||
mMap.put( 29, new float[]{ 0.0044444444f,0.0088888889f,0.0133333333f,0.0177777778f,0.0222222222f,0.0266666667f,0.0311111111f,0.0355555556f,0.04f,0.0444444444f,0.0488888889f,0.0533333333f,0.0577777778f,0.0622222222f,0.0666666667f,0.0622222222f,0.0577777778f,0.0533333333f,0.0488888889f,0.0444444444f,0.04f,0.0355555556f,0.0311111111f,0.0266666667f,0.0222222222f,0.0177777778f,0.0133333333f,0.0088888889f,0.0044444444f } );
|
||||
}
|
||||
|
||||
public TriangularSmoothingFilter()
|
||||
{
|
||||
super( mMap, 3 );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SmoothingType getSmoothingType()
|
||||
{
|
||||
return SmoothingType.TRIANGLE;
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
* 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 util;
|
||||
package dsp.mixer;
|
||||
|
||||
import sample.complex.Complex;
|
||||
|
||||
@@ -6,11 +6,9 @@ import instrument.tap.TapGroup;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import source.Source;
|
||||
import source.tuner.TunerChannelSource;
|
||||
import source.tuner.frequency.FrequencyCorrectionControl;
|
||||
import module.Module;
|
||||
import module.ProcessingChain;
|
||||
import source.Source;
|
||||
|
||||
public class InstrumentableProcessingChain extends ProcessingChain implements Instrumentable
|
||||
{
|
||||
|
||||
@@ -27,7 +27,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import message.IMessageListener;
|
||||
import message.IMessageProvider;
|
||||
import message.Message;
|
||||
import module.decode.Decoder;
|
||||
import module.decode.event.CallEvent;
|
||||
import module.decode.event.ICallEventListener;
|
||||
import module.decode.event.ICallEventProvider;
|
||||
@@ -42,6 +41,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.Broadcaster;
|
||||
import sample.Buffer;
|
||||
import sample.Listener;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import sample.complex.IComplexBufferListener;
|
||||
|
||||
@@ -19,7 +19,6 @@ package module.decode.ltrnet;
|
||||
|
||||
import module.decode.DecoderType;
|
||||
import module.decode.event.CallEvent;
|
||||
import module.decode.ltrstandard.LTRStandardMessage;
|
||||
import alias.Alias;
|
||||
import alias.AliasList;
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ import sample.real.RealBuffer;
|
||||
import alias.AliasList;
|
||||
import bits.MessageFramer;
|
||||
import bits.SyncPattern;
|
||||
import controller.channel.Channel.ChannelType;
|
||||
import dsp.filter.FilterFactory;
|
||||
import dsp.filter.Filters;
|
||||
import dsp.filter.Window.WindowType;
|
||||
|
||||
@@ -21,7 +21,6 @@ import java.awt.EventQueue;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
|
||||
import module.decode.p25.P25Decoder.Modulation;
|
||||
import module.decode.state.ChangedAttribute;
|
||||
import module.decode.state.DecoderPanel;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
@@ -36,7 +36,6 @@ import sample.complex.ComplexBufferToStreamConverter;
|
||||
import sample.complex.IComplexBufferListener;
|
||||
import source.tuner.frequency.FrequencyCorrectionControl;
|
||||
import alias.AliasList;
|
||||
import controller.channel.Channel.ChannelType;
|
||||
import dsp.filter.FilterFactory;
|
||||
import dsp.filter.Window.WindowType;
|
||||
import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
|
||||
|
||||
@@ -135,7 +135,7 @@ public class P25AudioModule extends Module implements Listener<Message>,
|
||||
library = (AudioConversionLibrary)temp.newInstance();
|
||||
|
||||
if( ( library.getMajorVersion() == 0 && library.getMinorVersion() >= 3 ) ||
|
||||
library.getMajorVersion() > 0 )
|
||||
library.getMajorVersion() >= 1 )
|
||||
{
|
||||
mAudioConverter = library.getAudioConverter( IMBE_CODEC,
|
||||
AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO );
|
||||
|
||||
@@ -30,10 +30,11 @@ import javax.swing.JPopupMenu;
|
||||
import javax.swing.JSeparator;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import playlist.PlaylistManager;
|
||||
import settings.ColorSetting.ColorSettingName;
|
||||
import settings.ColorSettingMenuItem;
|
||||
|
||||
@@ -7,20 +7,14 @@ import java.awt.GradientPaint;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Paint;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JSeparator;
|
||||
|
||||
import module.decode.Decoder;
|
||||
import module.decode.DecoderFactory;
|
||||
import module.decode.event.ActivitySummaryFrame;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
@@ -33,7 +33,6 @@ import source.tuner.frequency.FrequencyCorrectionControl;
|
||||
import source.tuner.frequency.IFrequencyCorrectionController;
|
||||
import dsp.filter.FilterFactory;
|
||||
import dsp.filter.Window.WindowType;
|
||||
import dsp.filter.dc.AveragingDCRemovalFilter_RB;
|
||||
import dsp.filter.dc.DCRemovalFilter_RB;
|
||||
import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
|
||||
import dsp.fm.FMDemodulator_CB;
|
||||
@@ -102,7 +101,7 @@ public class FMDemodulatorModule extends Module implements IComplexBufferListene
|
||||
|
||||
if( mRemoveDC )
|
||||
{
|
||||
mDCRemovalFilter = new AveragingDCRemovalFilter_RB( DC_REMOVAL_RATIO );
|
||||
mDCRemovalFilter = new DCRemovalFilter_RB( DC_REMOVAL_RATIO );
|
||||
mUnfilteredBufferBroadcaster.addListener( mDCRemovalFilter );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,13 +21,13 @@ import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import controller.channel.Channel;
|
||||
import module.Module;
|
||||
import module.log.MessageEventLogger.Type;
|
||||
import module.log.config.EventLogConfiguration;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import properties.SystemProperties;
|
||||
|
||||
public class EventLogManager
|
||||
|
||||
@@ -32,6 +32,7 @@ import module.Module;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.Buffer;
|
||||
import sample.ConversionUtils;
|
||||
import sample.Listener;
|
||||
import sample.complex.ComplexBuffer;
|
||||
@@ -163,7 +164,7 @@ public class ComplexBufferWaveRecorder extends Module
|
||||
{
|
||||
try
|
||||
{
|
||||
ComplexBuffer buffer = mBuffers.poll();
|
||||
Buffer buffer = mBuffers.poll();
|
||||
|
||||
while( buffer != null )
|
||||
{
|
||||
|
||||
@@ -30,9 +30,6 @@ import java.util.regex.Pattern;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class WaveWriter implements AutoCloseable
|
||||
{
|
||||
// private final static Logger mLog = LoggerFactory.getLogger( WaveWriter.class );
|
||||
|
||||
24
src/sample/Buffer.java
Normal file
24
src/sample/Buffer.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package sample;
|
||||
|
||||
public class Buffer
|
||||
{
|
||||
protected float[] mSamples;
|
||||
|
||||
public Buffer( float[] samples )
|
||||
{
|
||||
mSamples = samples;
|
||||
}
|
||||
|
||||
public float[] getSamples()
|
||||
{
|
||||
return mSamples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup method to nullify all data and references
|
||||
*/
|
||||
public void dispose()
|
||||
{
|
||||
mSamples = null;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
import sample.complex.ComplexBuffer;
|
||||
import sample.real.RealBuffer;
|
||||
|
||||
/**
|
||||
@@ -57,7 +56,7 @@ public class ConversionUtils
|
||||
* Converts the float samples in a complex buffer to a little endian 16-bit
|
||||
* buffer
|
||||
*/
|
||||
public static ByteBuffer convertToSigned16BitSamples( ComplexBuffer buffer )
|
||||
public static ByteBuffer convertToSigned16BitSamples( Buffer buffer )
|
||||
{
|
||||
return convertToSigned16BitSamples( buffer.getSamples() );
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
******************************************************************************/
|
||||
package sample.adapter;
|
||||
|
||||
public class ByteSampleAdapter extends SampleAdapter
|
||||
public class ByteSampleAdapter implements ISampleAdapter
|
||||
{
|
||||
private final static float[] LOOKUP_VALUES;
|
||||
|
||||
@@ -33,8 +33,6 @@ public class ByteSampleAdapter extends SampleAdapter
|
||||
{
|
||||
LOOKUP_VALUES[ x ] = (float)( x - 127 ) / 128.0f;
|
||||
}
|
||||
//
|
||||
// LOOKUP_VALUES[ 128 ] = 0.0000001f;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,7 +25,7 @@ import source.mixer.MixerChannel;
|
||||
/**
|
||||
* Converts 16-bit little endian byte data into an array of floats
|
||||
*/
|
||||
public class ChannelShortAdapter extends SampleAdapter
|
||||
public class ChannelShortAdapter implements ISampleAdapter
|
||||
{
|
||||
private ShortToFloatMap mMap = new ShortToFloatMap();
|
||||
private ByteOrder mByteOrder = ByteOrder.LITTLE_ENDIAN;
|
||||
|
||||
@@ -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
|
||||
@@ -21,7 +21,7 @@ package sample.adapter;
|
||||
* Adapter to convert byte data received from a data stream into float[]
|
||||
* samples
|
||||
*/
|
||||
public abstract class SampleAdapter
|
||||
public interface ISampleAdapter
|
||||
{
|
||||
public abstract float[] convert( byte[] samples );
|
||||
}
|
||||
@@ -23,7 +23,7 @@ import java.nio.ByteOrder;
|
||||
/**
|
||||
* Converts 16-bit little endian byte data into an array of floats
|
||||
*/
|
||||
public class ShortAdapter extends SampleAdapter
|
||||
public class ShortAdapter implements ISampleAdapter
|
||||
{
|
||||
private ShortToFloatMap mMap = new ShortToFloatMap();
|
||||
private ByteOrder mByteOrder = ByteOrder.LITTLE_ENDIAN;
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
package sample.complex;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class ComplexBuffer
|
||||
import sample.Buffer;
|
||||
|
||||
public class ComplexBuffer extends Buffer
|
||||
{
|
||||
private float[] mSamples;
|
||||
|
||||
/**
|
||||
* Wrapper around float array containing interleaved I/Q samples
|
||||
*/
|
||||
public ComplexBuffer( float[] samples )
|
||||
{
|
||||
mSamples = samples;
|
||||
super( samples );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -27,35 +23,4 @@ public class ComplexBuffer
|
||||
|
||||
return new ComplexBuffer( copy );
|
||||
}
|
||||
|
||||
/**
|
||||
* Interleaved I/Q float sample array
|
||||
*/
|
||||
public float[] getSamples()
|
||||
{
|
||||
return mSamples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this sample buffer into a list of complex samples
|
||||
*/
|
||||
public List<Complex> getComplexSamples()
|
||||
{
|
||||
List<Complex> samples = new ArrayList<Complex>();
|
||||
|
||||
for( int x = 0; x < mSamples.length; x += 2 )
|
||||
{
|
||||
samples.add( new Complex( mSamples[ x ], mSamples[ x + 1 ] ) );
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup method to nullify all data and references
|
||||
*/
|
||||
public void dispose()
|
||||
{
|
||||
mSamples = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,31 +2,18 @@ package sample.real;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class RealBuffer
|
||||
{
|
||||
private float[] mSamples;
|
||||
import sample.Buffer;
|
||||
|
||||
public class RealBuffer extends Buffer
|
||||
{
|
||||
/**
|
||||
* Wrapper around float array containing float samples
|
||||
* Wrapper around float array containing real float samples
|
||||
*/
|
||||
public RealBuffer( float[] samples )
|
||||
{
|
||||
mSamples = samples;
|
||||
super( samples );
|
||||
}
|
||||
|
||||
/**
|
||||
* Package private to allow subclass construction and subsequent definition
|
||||
* of the sample data
|
||||
*/
|
||||
RealBuffer()
|
||||
{
|
||||
}
|
||||
|
||||
protected void setSamples( float[] samples )
|
||||
{
|
||||
mSamples = samples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a deep copy of the buffer
|
||||
*/
|
||||
@@ -36,20 +23,4 @@ public class RealBuffer
|
||||
|
||||
return new RealBuffer( copy );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample data
|
||||
*/
|
||||
public float[] getSamples()
|
||||
{
|
||||
return mSamples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup method to nullify all data and references
|
||||
*/
|
||||
public void dispose()
|
||||
{
|
||||
mSamples = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import source.recording.RecordingConfiguration;
|
||||
import source.tuner.TunerConfiguration;
|
||||
import source.tuner.TunerConfigurationAssignment;
|
||||
import source.tuner.TunerType;
|
||||
import source.tuner.airspy.AirspyTunerConfiguration;
|
||||
import source.tuner.fcd.proV1.FCD1TunerConfiguration;
|
||||
import source.tuner.fcd.proplusV2.FCD2TunerConfiguration;
|
||||
import source.tuner.hackrf.HackRFTunerConfiguration;
|
||||
@@ -40,7 +41,8 @@ import source.tuner.rtl.r820t.R820TTunerConfiguration;
|
||||
/**
|
||||
* Support for persisting via JAXB all configured SDRTrunk settings
|
||||
*/
|
||||
@XmlSeeAlso( { ColorSetting.class,
|
||||
@XmlSeeAlso( { AirspyTunerConfiguration.class,
|
||||
ColorSetting.class,
|
||||
DefaultIcon.class,
|
||||
E4KTunerConfiguration.class,
|
||||
FCD1TunerConfiguration.class,
|
||||
|
||||
@@ -50,6 +50,7 @@ import source.recording.RecordingConfiguration;
|
||||
import source.tuner.TunerConfiguration;
|
||||
import source.tuner.TunerConfigurationAssignment;
|
||||
import source.tuner.TunerType;
|
||||
import source.tuner.airspy.AirspyTunerConfiguration;
|
||||
import source.tuner.fcd.proV1.FCD1TunerConfiguration;
|
||||
import source.tuner.fcd.proplusV2.FCD2TunerConfiguration;
|
||||
import source.tuner.hackrf.HackRFTunerConfiguration;
|
||||
@@ -573,6 +574,14 @@ public class SettingsManager
|
||||
{
|
||||
switch( type )
|
||||
{
|
||||
case AIRSPY_R820T:
|
||||
AirspyTunerConfiguration airspyConfig =
|
||||
new AirspyTunerConfiguration( name );
|
||||
|
||||
getSettings().addTunerConfiguration( airspyConfig );
|
||||
|
||||
save();
|
||||
return airspyConfig;
|
||||
case ELONICS_E4000:
|
||||
E4KTunerConfiguration e4KConfig =
|
||||
new E4KTunerConfiguration( name );
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,8 +13,12 @@ import javax.swing.JLayeredPane;
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JSeparator;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import sample.Listener;
|
||||
import sample.SampleType;
|
||||
@@ -31,10 +35,11 @@ import source.tuner.frequency.FrequencyChangeEvent.Attribute;
|
||||
import spectrum.converter.DFTResultsConverter;
|
||||
import spectrum.converter.RealDecibelConverter;
|
||||
import spectrum.menu.AveragingItem;
|
||||
import spectrum.menu.DBScaleItem;
|
||||
import spectrum.menu.FFTWidthItem;
|
||||
import spectrum.menu.FFTWindowTypeItem;
|
||||
import spectrum.menu.FrameRateItem;
|
||||
import spectrum.menu.SmoothingItem;
|
||||
import spectrum.menu.SmoothingTypeItem;
|
||||
import controller.ResourceManager;
|
||||
import controller.channel.Channel;
|
||||
import controller.channel.ChannelEvent;
|
||||
@@ -42,6 +47,9 @@ import controller.channel.ChannelEventListener;
|
||||
import dsp.filter.Filters;
|
||||
import dsp.filter.FloatHalfBandFilter;
|
||||
import dsp.filter.Window.WindowType;
|
||||
import dsp.filter.halfband.real.HalfBandFilter_RB_RB;
|
||||
import dsp.filter.hilbert.HilbertTransform;
|
||||
import dsp.filter.smoothing.SmoothingFilter.SmoothingType;
|
||||
|
||||
public class ChannelSpectrumPanel extends JPanel
|
||||
implements ChannelEventListener,
|
||||
@@ -49,9 +57,9 @@ public class ChannelSpectrumPanel extends JPanel
|
||||
SettingChangeListener,
|
||||
SpectralDisplayAdjuster
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String CHANNEL_SPECTRUM_AVERAGING_SIZE =
|
||||
"channel_spectrum_averaging_size";
|
||||
private static final Logger mLog = LoggerFactory.getLogger( ChannelSpectrumPanel.class );
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private ResourceManager mResourceManager;
|
||||
private DFTProcessor mDFTProcessor = new DFTProcessor( SampleType.REAL );
|
||||
@@ -62,13 +70,13 @@ public class ChannelSpectrumPanel extends JPanel
|
||||
|
||||
private Channel mCurrentChannel;
|
||||
|
||||
private int mSpectrumAveragingSize;
|
||||
private int mSampleBufferSize = 2400;
|
||||
private float[] mSamples = new float[ mSampleBufferSize ];
|
||||
private int mSamplePointer = 0;
|
||||
|
||||
private DecimatingSampleAssembler mDecimatingSampleAssembler;
|
||||
|
||||
private HalfBandFilter_RB_RB mDecimatingFilter = new HalfBandFilter_RB_RB(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 1.0f, true );
|
||||
|
||||
private AtomicBoolean mEnabled = new AtomicBoolean();
|
||||
|
||||
public ChannelSpectrumPanel( ResourceManager resourceManager )
|
||||
@@ -76,20 +84,12 @@ public class ChannelSpectrumPanel extends JPanel
|
||||
mResourceManager = resourceManager;
|
||||
mResourceManager.getSettingsManager().addListener( this );
|
||||
mSpectrumPanel = new SpectrumPanel( mResourceManager );
|
||||
mSpectrumPanel.setAveraging( 1 );
|
||||
|
||||
mOverlayPanel = new ChannelOverlayPanel( mResourceManager );
|
||||
|
||||
|
||||
mSpectrumAveragingSize = 10;
|
||||
// mSpectrumAveragingSize = mResourceManager.getSettingsManager()
|
||||
// .getIntegerSetting( CHANNEL_SPECTRUM_AVERAGING_SIZE, 10 ).getValue();
|
||||
|
||||
mSpectrumPanel.setAveraging( mSpectrumAveragingSize );
|
||||
|
||||
mDFTProcessor.addConverter( mDFTConverter );
|
||||
mDFTConverter.addListener( mSpectrumPanel );
|
||||
|
||||
mDecimatingSampleAssembler = new DecimatingSampleAssembler( mDFTProcessor );
|
||||
|
||||
/* Set the DFTProcessor to the decimated 24kHz sample rate */
|
||||
mDFTProcessor.frequencyChanged(
|
||||
new FrequencyChangeEvent( Attribute.SAMPLE_RATE, 24000 ) );
|
||||
@@ -254,10 +254,11 @@ public class ChannelSpectrumPanel extends JPanel
|
||||
@Override
|
||||
public void receive( RealBuffer buffer )
|
||||
{
|
||||
for( float sample: buffer.getSamples() )
|
||||
{
|
||||
mDecimatingSampleAssembler.receive( sample );
|
||||
}
|
||||
RealBuffer decimated = mDecimatingFilter.filter( buffer );
|
||||
|
||||
//Hack: we're placing real samples in a complex buffer that the DFT
|
||||
//processor is expecting.
|
||||
mDFTProcessor.receive( new ComplexBuffer( decimated.getSamples() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -367,17 +368,9 @@ public class ChannelSpectrumPanel extends JPanel
|
||||
*/
|
||||
JMenu averagingMenu = new JMenu( "Averaging" );
|
||||
averagingMenu.add(
|
||||
new AveragingItem( ChannelSpectrumPanel.this, 4 ) );
|
||||
new AveragingItem( ChannelSpectrumPanel.this, 2 ) );
|
||||
displayMenu.add( averagingMenu );
|
||||
|
||||
/**
|
||||
* Baseline menu
|
||||
*/
|
||||
JMenu baselineMenu = new JMenu( "Baseline" );
|
||||
baselineMenu.add(
|
||||
new DBScaleItem( ChannelSpectrumPanel.this, 50 ) );
|
||||
displayMenu.add( baselineMenu );
|
||||
|
||||
/**
|
||||
* FFT width
|
||||
*/
|
||||
@@ -416,6 +409,24 @@ public class ChannelSpectrumPanel extends JPanel
|
||||
new FFTWindowTypeItem( mDFTProcessor, type ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Smoothing menu
|
||||
*/
|
||||
JMenu smoothingMenu = new JMenu( "Smoothing" );
|
||||
|
||||
if( mSpectrumPanel.getSmoothingType() != SmoothingType.NONE )
|
||||
{
|
||||
smoothingMenu.add( new SmoothingItem( ChannelSpectrumPanel.this, 5 ) );
|
||||
smoothingMenu.add( new JSeparator() );
|
||||
}
|
||||
|
||||
smoothingMenu.add( new SmoothingTypeItem( ChannelSpectrumPanel.this, SmoothingType.GAUSSIAN ) );
|
||||
smoothingMenu.add( new SmoothingTypeItem( ChannelSpectrumPanel.this, SmoothingType.TRIANGLE ) );
|
||||
smoothingMenu.add( new SmoothingTypeItem( ChannelSpectrumPanel.this, SmoothingType.RECTANGLE ) );
|
||||
smoothingMenu.add( new SmoothingTypeItem( ChannelSpectrumPanel.this, SmoothingType.NONE ) );
|
||||
|
||||
displayMenu.add( smoothingMenu );
|
||||
|
||||
if( contextMenu != null )
|
||||
{
|
||||
contextMenu.show( mOverlayPanel,
|
||||
@@ -443,37 +454,35 @@ public class ChannelSpectrumPanel extends JPanel
|
||||
mSpectrumPanel.setAveraging( averaging );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDBScale()
|
||||
{
|
||||
return mSpectrumPanel.getDBScale();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDBScale( int baseline )
|
||||
{
|
||||
mSpectrumPanel.setDBScale( baseline );
|
||||
}
|
||||
|
||||
public class DecimatingSampleAssembler
|
||||
public void setSampleSize( double sampleSize )
|
||||
{
|
||||
private FloatHalfBandFilter mDecimationFilter = new FloatHalfBandFilter(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002f );
|
||||
|
||||
private SampleAssembler mSampleAssembler;
|
||||
|
||||
public DecimatingSampleAssembler( Listener<ComplexBuffer> listener )
|
||||
{
|
||||
mSampleAssembler = new SampleAssembler( listener );
|
||||
mDecimationFilter.setListener( mSampleAssembler );
|
||||
}
|
||||
|
||||
public void receive( float sample )
|
||||
{
|
||||
mDecimationFilter.receive( sample );
|
||||
}
|
||||
mSpectrumPanel.setSampleSize( sampleSize );
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getSmoothing()
|
||||
{
|
||||
return mSpectrumPanel.getSmoothing();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSmoothing( int smoothing )
|
||||
{
|
||||
mSpectrumPanel.setSmoothing( smoothing );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SmoothingType getSmoothingType()
|
||||
{
|
||||
return mSpectrumPanel.getSmoothingType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSmoothingType( SmoothingType type )
|
||||
{
|
||||
mSpectrumPanel.setSmoothingType( type );
|
||||
}
|
||||
|
||||
public class SampleAssembler implements RealSampleListener
|
||||
{
|
||||
private Listener<ComplexBuffer> mListener;
|
||||
|
||||
@@ -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
|
||||
@@ -30,13 +30,14 @@ import org.jtransforms.fft.FloatFFT_1D;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import controller.NamingThreadFactory;
|
||||
import sample.Buffer;
|
||||
import sample.Listener;
|
||||
import sample.SampleType;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import source.tuner.frequency.FrequencyChangeEvent;
|
||||
import source.tuner.frequency.FrequencyChangeListener;
|
||||
import spectrum.converter.DFTResultsConverter;
|
||||
import controller.NamingThreadFactory;
|
||||
import dsp.filter.Window;
|
||||
import dsp.filter.Window.WindowType;
|
||||
|
||||
@@ -44,8 +45,7 @@ import dsp.filter.Window.WindowType;
|
||||
* Processes both complex samples or float samples and dispatches a float array
|
||||
* of DFT results, using configurable fft size and output dispatch timelines.
|
||||
*/
|
||||
public class DFTProcessor implements Listener<ComplexBuffer>,
|
||||
FrequencyChangeListener
|
||||
public class DFTProcessor implements Listener<ComplexBuffer>, FrequencyChangeListener
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( DFTProcessor.class );
|
||||
@@ -54,7 +54,7 @@ public class DFTProcessor implements Listener<ComplexBuffer>,
|
||||
new CopyOnWriteArrayList<DFTResultsConverter>();
|
||||
|
||||
private ArrayBlockingQueue<ComplexBuffer> mQueue =
|
||||
new ArrayBlockingQueue<ComplexBuffer>( 200 );
|
||||
new ArrayBlockingQueue<ComplexBuffer>( 200 );
|
||||
|
||||
private ScheduledExecutorService mScheduler = Executors
|
||||
.newScheduledThreadPool( 1, new NamingThreadFactory( "spectrum dft" ) );
|
||||
@@ -74,7 +74,6 @@ public class DFTProcessor implements Listener<ComplexBuffer>,
|
||||
private int mSampleRate;
|
||||
private int mFFTFloatsPerFrame;
|
||||
private float mNewFloatsPerFrame;
|
||||
private float mNewFloatsPerFrameToConsume;
|
||||
private float mNewFloatResidual;
|
||||
private float[] mPreviousFrame = new float[ 8192 ];
|
||||
|
||||
@@ -231,7 +230,7 @@ public class DFTProcessor implements Listener<ComplexBuffer>,
|
||||
if( !mQueue.offer( sampleBuffer ) )
|
||||
{
|
||||
mLog.error( "DFTProcessor - [" + mSampleType.toString()
|
||||
+ "]queue is full, purging queue, "
|
||||
+ "] queue is full, purging queue, "
|
||||
+ "samples[" + sampleBuffer + "]" );
|
||||
|
||||
mQueue.clear();
|
||||
@@ -245,7 +244,7 @@ public class DFTProcessor implements Listener<ComplexBuffer>,
|
||||
|
||||
try
|
||||
{
|
||||
ComplexBuffer buffer = mQueue.take();
|
||||
Buffer buffer = mQueue.take();
|
||||
mCurrentBuffer = buffer.getSamples();
|
||||
}
|
||||
catch ( InterruptedException e )
|
||||
@@ -500,14 +499,5 @@ public class DFTProcessor implements Listener<ComplexBuffer>,
|
||||
mFFTFloatsPerFrame = ( mSampleType == SampleType.COMPLEX ?
|
||||
mFFTWidth.getWidth() * 2 :
|
||||
mFFTWidth.getWidth() );
|
||||
|
||||
if( mFFTFloatsPerFrame < mNewFloatsPerFrame )
|
||||
{
|
||||
mNewFloatsPerFrameToConsume = mFFTFloatsPerFrame;
|
||||
}
|
||||
else
|
||||
{
|
||||
mNewFloatsPerFrameToConsume = mNewFloatsPerFrame;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,30 @@
|
||||
package spectrum;
|
||||
|
||||
import dsp.filter.smoothing.SmoothingFilter.SmoothingType;
|
||||
|
||||
public interface SpectralDisplayAdjuster
|
||||
{
|
||||
/**
|
||||
/**
|
||||
* Gets the averaging value indicating the number of FFT results that are
|
||||
* averaged to produce each FFT results output.
|
||||
*
|
||||
* @return averaging value ( 2 - 50 )
|
||||
*/
|
||||
public int getAveraging();
|
||||
|
||||
/**
|
||||
* Sets the averaging value indicating the number of FFT results to average
|
||||
* when producing an FFT output result.
|
||||
* @param averaging value ( 2 - 50 )
|
||||
*/
|
||||
public void setAveraging( int averaging );
|
||||
|
||||
/**
|
||||
* Gets the dBm scale used when plotting the FFT bin results in the FFT
|
||||
* spectral display. This value determines the smallest displayed power
|
||||
* spectrum value that is shown at the bottom of the display.
|
||||
* Sets the smoothing filter averaging window width.
|
||||
*
|
||||
* @return averaging value ( 1 - 50 )
|
||||
* Valid values are odd in the range 3 - 29
|
||||
*/
|
||||
public int getDBScale();
|
||||
|
||||
public int getSmoothing();
|
||||
public void setSmoothing( int smoothing );
|
||||
|
||||
/**
|
||||
* Sets the dBm scale from 0 (top of display) to the scale value (bottom).
|
||||
*
|
||||
* @param baseline value ( -160 to -30 dBm )
|
||||
* Sets the type of smoothing filter to use.
|
||||
*/
|
||||
public void setDBScale( int baseline );
|
||||
public SmoothingType getSmoothingType();
|
||||
public void setSmoothingType( SmoothingType type );
|
||||
|
||||
}
|
||||
|
||||
@@ -66,10 +66,11 @@ import spectrum.OverlayPanel.ChannelDisplay;
|
||||
import spectrum.converter.ComplexDecibelConverter;
|
||||
import spectrum.converter.DFTResultsConverter;
|
||||
import spectrum.menu.AveragingItem;
|
||||
import spectrum.menu.DBScaleItem;
|
||||
import spectrum.menu.FFTWidthItem;
|
||||
import spectrum.menu.FFTWindowTypeItem;
|
||||
import spectrum.menu.FrameRateItem;
|
||||
import spectrum.menu.SmoothingItem;
|
||||
import spectrum.menu.SmoothingTypeItem;
|
||||
|
||||
import com.jidesoft.swing.JideSplitPane;
|
||||
|
||||
@@ -79,6 +80,7 @@ import controller.channel.ChannelEvent;
|
||||
import controller.channel.ChannelEventListener;
|
||||
import controller.channel.ChannelUtils;
|
||||
import dsp.filter.Window.WindowType;
|
||||
import dsp.filter.smoothing.SmoothingFilter.SmoothingType;
|
||||
|
||||
/**
|
||||
* Spectral Display Panel - comprises a DFTProcessor coupled with a waterfall
|
||||
@@ -387,6 +389,9 @@ public class SpectralDisplayPanel extends JPanel
|
||||
//Register the dft processor to receive samples from the tuner
|
||||
mTuner.addListener( (Listener<ComplexBuffer>)mDFTProcessor );
|
||||
|
||||
mDFTConverter.setSampleSize( mTuner.getSampleSize() );
|
||||
mSpectrumPanel.setSampleSize( mTuner.getSampleSize() );
|
||||
|
||||
//Fire frequency and sample rate change events so that everyone
|
||||
//can init
|
||||
try
|
||||
@@ -597,14 +602,6 @@ public class SpectralDisplayPanel extends JPanel
|
||||
|
||||
if( event.getComponent() != mWaterfallPanel )
|
||||
{
|
||||
/**
|
||||
* DB Scale menu
|
||||
*/
|
||||
JMenu dbScaleMenu = new JMenu( "Scale: 0 to dBm" );
|
||||
dbScaleMenu.add(
|
||||
new DBScaleItem( mSpectrumPanel, -100 ) );
|
||||
displayMenu.add( dbScaleMenu );
|
||||
|
||||
/**
|
||||
* Averaging menu
|
||||
*/
|
||||
@@ -666,6 +663,26 @@ public class SpectralDisplayPanel extends JPanel
|
||||
fftWindowType.add(
|
||||
new FFTWindowTypeItem( mDFTProcessor, type ) );
|
||||
}
|
||||
|
||||
if( event.getComponent() != mWaterfallPanel )
|
||||
{
|
||||
/**
|
||||
* Smoothing menu
|
||||
*/
|
||||
JMenu smoothingMenu = new JMenu( "Smoothing" );
|
||||
|
||||
if( mSpectrumPanel.getSmoothingType() != SmoothingType.NONE )
|
||||
{
|
||||
smoothingMenu.add( new SmoothingItem( mSpectrumPanel, 5 ) );
|
||||
smoothingMenu.add( new JSeparator() );
|
||||
}
|
||||
smoothingMenu.add( new SmoothingTypeItem( mSpectrumPanel, SmoothingType.GAUSSIAN ) );
|
||||
smoothingMenu.add( new SmoothingTypeItem( mSpectrumPanel, SmoothingType.TRIANGLE ) );
|
||||
smoothingMenu.add( new SmoothingTypeItem( mSpectrumPanel, SmoothingType.RECTANGLE ) );
|
||||
smoothingMenu.add( new SmoothingTypeItem( mSpectrumPanel, SmoothingType.NONE ) );
|
||||
|
||||
displayMenu.add( smoothingMenu );
|
||||
}
|
||||
|
||||
if( contextMenu != null )
|
||||
{
|
||||
|
||||
@@ -36,8 +36,13 @@ import settings.ColorSetting;
|
||||
import settings.ColorSetting.ColorSettingName;
|
||||
import settings.Setting;
|
||||
import settings.SettingChangeListener;
|
||||
import buffer.FloatArrayCircularAveragingBuffer;
|
||||
import controller.ResourceManager;
|
||||
import dsp.filter.smoothing.GaussianSmoothingFilter;
|
||||
import dsp.filter.smoothing.NoSmoothingFilter;
|
||||
import dsp.filter.smoothing.RectangularSmoothingFilter;
|
||||
import dsp.filter.smoothing.SmoothingFilter;
|
||||
import dsp.filter.smoothing.SmoothingFilter.SmoothingType;
|
||||
import dsp.filter.smoothing.TriangularSmoothingFilter;
|
||||
|
||||
public class SpectrumPanel extends JPanel
|
||||
implements DFTResultsListener,
|
||||
@@ -51,9 +56,9 @@ public class SpectrumPanel extends JPanel
|
||||
/* Set display bins size to 1, so that we're guaranteed a reset to the
|
||||
* correct width once the first sample set arrives */
|
||||
private float[] mDisplayFFTBins = new float[ 1 ];
|
||||
private FloatArrayCircularAveragingBuffer mFFTAveragingBuffer;
|
||||
private int mAveraging = 4;
|
||||
private int mDBScale = -100; //dBm
|
||||
|
||||
private float mDBScale;
|
||||
|
||||
/**
|
||||
* Spectral Display Color Settings
|
||||
@@ -65,6 +70,11 @@ public class SpectrumPanel extends JPanel
|
||||
private static final String SPECTRUM_AVERAGING_SIZE = "spectrum_averaging_size";
|
||||
private static final int SPECTRUM_TRANSLUCENCY = 128;
|
||||
|
||||
private static final RenderingHints RENDERING_HINTS =
|
||||
new RenderingHints( RenderingHints.KEY_ANTIALIASING,
|
||||
RenderingHints.VALUE_ANTIALIAS_ON );
|
||||
|
||||
|
||||
|
||||
private Color mColorSpectrumBackground;
|
||||
private Color mColorSpectrumGradientTop;
|
||||
@@ -73,11 +83,20 @@ public class SpectrumPanel extends JPanel
|
||||
|
||||
private float mSpectrumInset = 20.0f;
|
||||
|
||||
private SmoothingFilter mSmoothingFilter = new GaussianSmoothingFilter();
|
||||
|
||||
private ResourceManager mResourceManager;
|
||||
|
||||
public SpectrumPanel( ResourceManager resourceManager )
|
||||
{
|
||||
mResourceManager = resourceManager;
|
||||
|
||||
RENDERING_HINTS.put( RenderingHints.KEY_RENDERING,
|
||||
RenderingHints.VALUE_RENDER_QUALITY );
|
||||
|
||||
setSampleSize( 16.0 );
|
||||
|
||||
mSmoothingFilter.setPointSize( SmoothingFilter.SMOOTHING_DEFAULT );
|
||||
|
||||
getSettings();
|
||||
|
||||
@@ -89,12 +108,16 @@ public class SpectrumPanel extends JPanel
|
||||
mResourceManager = null;
|
||||
}
|
||||
|
||||
public void setSampleSize( double sampleSize )
|
||||
{
|
||||
assert( 2.0 <= sampleSize && sampleSize <= 32.0 );
|
||||
|
||||
mDBScale = -(float)( 20.0 * Math.log10( Math.pow( 2.0, sampleSize ) ) );
|
||||
}
|
||||
|
||||
public void setAveraging( int size )
|
||||
{
|
||||
mAveraging = size;
|
||||
|
||||
mFFTAveragingBuffer =
|
||||
new FloatArrayCircularAveragingBuffer( mAveraging );
|
||||
}
|
||||
|
||||
public int getAveraging()
|
||||
@@ -102,19 +125,6 @@ public class SpectrumPanel extends JPanel
|
||||
return mAveraging;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the lowest decibel scale value to show on the display
|
||||
*/
|
||||
public void setDBScale( int dbScale )
|
||||
{
|
||||
mDBScale = dbScale;
|
||||
}
|
||||
|
||||
public int getDBScale()
|
||||
{
|
||||
return mDBScale;
|
||||
}
|
||||
|
||||
public void clearSpectrum()
|
||||
{
|
||||
mDisplayFFTBins = null;
|
||||
@@ -180,18 +190,36 @@ public class SpectrumPanel extends JPanel
|
||||
*/
|
||||
public void receive( float[] currentFFTBins )
|
||||
{
|
||||
if( Float.isInfinite( currentFFTBins[ 0 ] ) || Float.isNaN( currentFFTBins[ 0 ] ) )
|
||||
{
|
||||
currentFFTBins = new float[ currentFFTBins.length ];
|
||||
}
|
||||
|
||||
//Construct and/or resize our fft results variables
|
||||
if( mDisplayFFTBins == null ||
|
||||
mDisplayFFTBins.length != currentFFTBins.length )
|
||||
{
|
||||
mDisplayFFTBins = new float[ currentFFTBins.length ];
|
||||
mDisplayFFTBins = currentFFTBins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the new FFT bins in the buffer and get the average of the FFT
|
||||
* bin buffer contents into the display fft bins variable
|
||||
*/
|
||||
mDisplayFFTBins = mFFTAveragingBuffer.get( currentFFTBins );
|
||||
//Smooth across the bins
|
||||
float[] smoothedBins = mSmoothingFilter.filter( currentFFTBins );
|
||||
|
||||
//Average bins over multiple frames
|
||||
if( mAveraging > 1 )
|
||||
{
|
||||
float gain = 1.0f / (float)mAveraging;
|
||||
|
||||
for( int x = 0; x < mDisplayFFTBins.length; x++ )
|
||||
{
|
||||
mDisplayFFTBins[ x ] += ( smoothedBins[ x ] - mDisplayFFTBins[ x ] ) * gain;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mDisplayFFTBins = smoothedBins;
|
||||
}
|
||||
|
||||
|
||||
repaint();
|
||||
}
|
||||
@@ -204,14 +232,8 @@ public class SpectrumPanel extends JPanel
|
||||
Graphics2D graphics = (Graphics2D) g;
|
||||
graphics.setBackground( mColorSpectrumBackground );
|
||||
|
||||
RenderingHints renderHints =
|
||||
new RenderingHints( RenderingHints.KEY_ANTIALIASING,
|
||||
RenderingHints.VALUE_ANTIALIAS_ON );
|
||||
|
||||
renderHints.put( RenderingHints.KEY_RENDERING,
|
||||
RenderingHints.VALUE_RENDER_QUALITY );
|
||||
|
||||
graphics.setRenderingHints( renderHints );
|
||||
graphics.setRenderingHints( RENDERING_HINTS );
|
||||
|
||||
drawSpectrum( graphics );
|
||||
}
|
||||
@@ -231,7 +253,7 @@ public class SpectrumPanel extends JPanel
|
||||
|
||||
//Define the gradient
|
||||
GradientPaint gradient = new GradientPaint( 0,
|
||||
0,
|
||||
getSize().height / 4,
|
||||
mColorSpectrumGradientTop,
|
||||
0,
|
||||
getSize().height,
|
||||
@@ -253,7 +275,7 @@ public class SpectrumPanel extends JPanel
|
||||
{
|
||||
float insideHeight = size.height - mSpectrumInset;
|
||||
|
||||
float scalor = insideHeight / ( -mDBScale );
|
||||
float scalor = insideHeight / mDBScale;
|
||||
|
||||
float insideWidth = size.width;
|
||||
|
||||
@@ -271,7 +293,7 @@ public class SpectrumPanel extends JPanel
|
||||
}
|
||||
else
|
||||
{
|
||||
height = (float)( Math.abs( mDisplayFFTBins[ x ] ) * scalor );
|
||||
height = mDisplayFFTBins[ x ] * scalor;
|
||||
|
||||
if( height > insideHeight )
|
||||
{
|
||||
@@ -316,5 +338,54 @@ public class SpectrumPanel extends JPanel
|
||||
}
|
||||
|
||||
@Override
|
||||
public void settingDeleted( Setting setting ) {}
|
||||
public void settingDeleted( Setting setting ) { /* not implemented */ }
|
||||
|
||||
@Override
|
||||
public int getSmoothing()
|
||||
{
|
||||
return mSmoothingFilter.getPointSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSmoothing( int smoothing )
|
||||
{
|
||||
mSmoothingFilter.setPointSize( smoothing );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SmoothingType getSmoothingType()
|
||||
{
|
||||
return mSmoothingFilter.getSmoothingType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSmoothingType( SmoothingType type )
|
||||
{
|
||||
if( mSmoothingFilter.getSmoothingType() != type )
|
||||
{
|
||||
int pointSize = getSmoothing();
|
||||
|
||||
synchronized ( mSmoothingFilter )
|
||||
{
|
||||
switch( type )
|
||||
{
|
||||
case GAUSSIAN:
|
||||
mSmoothingFilter = new GaussianSmoothingFilter();
|
||||
break;
|
||||
case RECTANGLE:
|
||||
mSmoothingFilter = new RectangularSmoothingFilter();
|
||||
break;
|
||||
case TRIANGLE:
|
||||
mSmoothingFilter = new TriangularSmoothingFilter();
|
||||
break;
|
||||
case NONE:
|
||||
default:
|
||||
mSmoothingFilter = new NoSmoothingFilter();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setSmoothing( pointSize );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
/*******************************************************************************
|
||||
* 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 spectrum.converter;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -6,52 +23,60 @@ import org.slf4j.LoggerFactory;
|
||||
public class ComplexDecibelConverter extends DFTResultsConverter
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( DFTResultsConverter.class );
|
||||
LoggerFactory.getLogger( ComplexDecibelConverter.class );
|
||||
|
||||
private float mDynamicRangeReference = (float)Math.pow( 2.0, 16.0 );
|
||||
|
||||
/**
|
||||
* Converts the output of the JTransforms FloatFFT_1D.complexForward()
|
||||
* calculation into a normalized power spectrum in decibels, per description
|
||||
* in Lyons, Understanding Digital Signal Processing, 3e, page 141.
|
||||
* calculation into the power spectrum in decibels, normalized to the
|
||||
* sample bit depth.
|
||||
*/
|
||||
public ComplexDecibelConverter()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the bit depth to establish the maximum dynamic range. All
|
||||
* FFT bin values will be scaled according to this value.
|
||||
*/
|
||||
@Override
|
||||
public void setSampleSize( double size )
|
||||
{
|
||||
assert( 2.0 <= size && size <= 32.0 );
|
||||
|
||||
mDynamicRangeReference = (float)Math.pow( 2.0d, size );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( float[] results )
|
||||
{
|
||||
float[] processed = new float[ results.length / 2 ];
|
||||
|
||||
/* Output from JTransforms 3.0 has the lower and upper halves of the
|
||||
* spectrum swapped. */
|
||||
int half = processed.length / 2;
|
||||
|
||||
|
||||
for( int x = 0; x < results.length; x += 2 )
|
||||
{
|
||||
float magnitude = (float)Math.sqrt( results[ x ] *
|
||||
results[ x ] + results[ x + 1 ] * results[ x + 1 ] );
|
||||
|
||||
/* Place upper half of magnitudes in lower half of results, vice-versa */
|
||||
//Calculate the magnitude squared value from each bin's real and
|
||||
//imaginary value, scale it to the available dynamic range and
|
||||
//convert to dB.
|
||||
float normalizedMagnitude = 10.0f * (float)Math.log10(
|
||||
( ( results[ x ] * results[ x ] ) +
|
||||
( results[ x + 1 ] * results[ x + 1 ] ) ) /
|
||||
mDynamicRangeReference );
|
||||
|
||||
// We have to swap the upper and lower halves of the JTransforms
|
||||
// DFT results for correct display
|
||||
if( x / 2 >= half )
|
||||
{
|
||||
processed[ x / 2 - half ] = magnitude;
|
||||
processed[ x / 2 - half ] = normalizedMagnitude;
|
||||
}
|
||||
else
|
||||
{
|
||||
processed[ x / 2 + half ] = magnitude;
|
||||
processed[ x / 2 + half ] = normalizedMagnitude;
|
||||
}
|
||||
}
|
||||
|
||||
/* Use the DC component as the largest magnitude value to keep the
|
||||
* display from jumping when a strong bursting signal exists */
|
||||
float largestMagnitude = processed[ half ];
|
||||
|
||||
if( largestMagnitude > 0 )
|
||||
{
|
||||
/* Convert magnitudes to normized power spectrum dBm */
|
||||
for( int i = 0; i < processed.length; i++ )
|
||||
{
|
||||
processed[ i ] = 20.0f *
|
||||
(float)Math.log10( processed[ i ] / largestMagnitude );
|
||||
}
|
||||
|
||||
dispatch( processed );
|
||||
}
|
||||
dispatch( processed );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,12 @@ public abstract class DFTResultsConverter
|
||||
public DFTResultsConverter()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the sample size in bits to establish the maximum dynamic range.
|
||||
* All FFT bin values will be scaled according to this value.
|
||||
*/
|
||||
public abstract void setSampleSize( double size );
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
|
||||
@@ -1,7 +1,31 @@
|
||||
package spectrum.converter;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class RealDecibelConverter extends DFTResultsConverter
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger( RealDecibelConverter.class );
|
||||
|
||||
private float mDynamicRangeReference;
|
||||
|
||||
public RealDecibelConverter()
|
||||
{
|
||||
setSampleSize( 24.0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the bit depth to establish the maximum dynamic range. All
|
||||
* FFT bin values will be scaled according to this value.
|
||||
*/
|
||||
@Override
|
||||
public void setSampleSize( double size )
|
||||
{
|
||||
assert( 2.0 <= size && size <= 32.0 );
|
||||
|
||||
mDynamicRangeReference = (float)Math.pow( 2.0d, size );
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the output of the JTransforms FloatFFT_1D.realForward()
|
||||
* calculation into a normalized power spectrum in decibels, per description
|
||||
@@ -10,29 +34,22 @@ public class RealDecibelConverter extends DFTResultsConverter
|
||||
@Override
|
||||
public void receive( float[] results )
|
||||
{
|
||||
float dc = Math.abs( results[ 0 ] );
|
||||
float[] processed = new float[ results.length / 4 ];
|
||||
|
||||
/* Ensure we have power in the DC power bin (bin 0), otherwise don't
|
||||
* dispatch the results */
|
||||
if( dc != 0 )
|
||||
int index = 0;
|
||||
|
||||
for( int x = 0; x < processed.length; x ++ )
|
||||
{
|
||||
/* Note: we're only processing 1/4 of the output samples */
|
||||
float[] processed = new float[ results.length / 4 + 1 ];
|
||||
index = x * 2;
|
||||
|
||||
for( int x = 0; x < processed.length - 2; x ++ )
|
||||
{
|
||||
int index = ( x + 1 ) * 2;
|
||||
float normalizedMagnitude = 10.0f * (float)Math.log10(
|
||||
( ( results[ index ] * results[ index ] ) +
|
||||
( results[ index + 1 ] * results[ index + 1 ] ) ) /
|
||||
mDynamicRangeReference );
|
||||
|
||||
double magnitude = ( ( results[ index ] * results[ index ] ) +
|
||||
( results[ index + 1 ] * results[ index + 1 ] ) );
|
||||
|
||||
processed[ x ] = 10.0f *
|
||||
(float)( Math.log10( magnitude / dc ) );
|
||||
}
|
||||
|
||||
processed[ processed.length - 1 ] = processed[ 0 ];
|
||||
|
||||
dispatch( processed );
|
||||
processed[ x ] = normalizedMagnitude;
|
||||
}
|
||||
|
||||
dispatch( processed );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,14 +18,15 @@ public class AveragingItem extends JSlider implements ChangeListener
|
||||
|
||||
public AveragingItem( SpectralDisplayAdjuster adjuster, int defaultValue )
|
||||
{
|
||||
super( JSlider.HORIZONTAL, 2, 50, adjuster.getAveraging() );
|
||||
super( JSlider.HORIZONTAL, 1, 20, adjuster.getAveraging() );
|
||||
mDefaultValue = defaultValue;
|
||||
|
||||
mAdjuster = adjuster;
|
||||
|
||||
setMajorTickSpacing( 10 );
|
||||
setMinorTickSpacing( 5 );
|
||||
setMajorTickSpacing( 5 );
|
||||
setMinorTickSpacing( 1 );
|
||||
setPaintTicks( true );
|
||||
setPaintLabels( true );
|
||||
|
||||
addChangeListener( this );
|
||||
|
||||
|
||||
@@ -2,34 +2,50 @@ package spectrum.menu;
|
||||
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.util.Hashtable;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JSlider;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
|
||||
import spectrum.SpectralDisplayAdjuster;
|
||||
import dsp.filter.smoothing.SmoothingFilter;
|
||||
|
||||
public class DBScaleItem extends JSlider implements ChangeListener
|
||||
public class SmoothingItem extends JSlider implements ChangeListener
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private SpectralDisplayAdjuster mAdjuster;
|
||||
private int mDefaultValue;
|
||||
|
||||
public DBScaleItem( SpectralDisplayAdjuster adjuster, int defaultValue )
|
||||
public SmoothingItem( SpectralDisplayAdjuster adjuster, int defaultValue )
|
||||
{
|
||||
super( JSlider.HORIZONTAL, -160, -30, adjuster.getDBScale() );
|
||||
mDefaultValue = defaultValue;
|
||||
super( JSlider.HORIZONTAL,
|
||||
SmoothingFilter.SMOOTHING_MINIMUM,
|
||||
SmoothingFilter.SMOOTHING_MAXIMUM,
|
||||
adjuster.getSmoothing() );
|
||||
|
||||
mDefaultValue = defaultValue;
|
||||
mAdjuster = adjuster;
|
||||
|
||||
setMajorTickSpacing( 40 );
|
||||
setMinorTickSpacing( 10 );
|
||||
setSnapToTicks( true );
|
||||
setMajorTickSpacing( 6 );
|
||||
setMinorTickSpacing( 2 );
|
||||
setPaintTicks( true );
|
||||
setPaintLabels( true );
|
||||
|
||||
addChangeListener( this );
|
||||
Hashtable<Integer,JLabel> labels = new Hashtable<>();
|
||||
labels.put( new Integer( 3 ), new JLabel( "3" ) );
|
||||
labels.put( new Integer( 9 ), new JLabel( "9" ) );
|
||||
labels.put( new Integer( 15 ), new JLabel( "15" ) );
|
||||
labels.put( new Integer( 21 ), new JLabel( "21" ) );
|
||||
labels.put( new Integer( 27 ), new JLabel( "27" ) );
|
||||
|
||||
setLabelTable( labels );
|
||||
|
||||
addChangeListener( this );
|
||||
|
||||
addMouseListener( new MouseListener()
|
||||
{
|
||||
@Override
|
||||
@@ -37,7 +53,7 @@ public class DBScaleItem extends JSlider implements ChangeListener
|
||||
{
|
||||
if( event.getClickCount() == 2 )
|
||||
{
|
||||
DBScaleItem.this.setValue( mDefaultValue );
|
||||
SmoothingItem.this.setValue( mDefaultValue );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +68,10 @@ public class DBScaleItem extends JSlider implements ChangeListener
|
||||
public void stateChanged( ChangeEvent event )
|
||||
{
|
||||
int value = ((JSlider)event.getSource()).getValue();
|
||||
|
||||
mAdjuster.setDBScale( value );
|
||||
|
||||
if( value % 2 == 1 )
|
||||
{
|
||||
mAdjuster.setSmoothing( value );
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/spectrum/menu/SmoothingTypeItem.java
Normal file
36
src/spectrum/menu/SmoothingTypeItem.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package spectrum.menu;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.JCheckBoxMenuItem;
|
||||
|
||||
import spectrum.SpectralDisplayAdjuster;
|
||||
import dsp.filter.smoothing.SmoothingFilter.SmoothingType;
|
||||
|
||||
public class SmoothingTypeItem extends JCheckBoxMenuItem
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private SpectralDisplayAdjuster mAdjuster;
|
||||
private SmoothingType mSmoothingType;
|
||||
|
||||
public SmoothingTypeItem( SpectralDisplayAdjuster adjuster, SmoothingType type )
|
||||
{
|
||||
super( type.name() );
|
||||
|
||||
mAdjuster = adjuster;
|
||||
mSmoothingType = type;
|
||||
|
||||
setSelected( mAdjuster.getSmoothingType() == type );
|
||||
|
||||
addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
mAdjuster.setSmoothingType( mSmoothingType );
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user