mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
Resolves issue with correct display and scaling of decibel values in
both the spectral and waterfall displays. Resolves issue with gain loss in HilbertTransform.
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
an alias identifier having the lowest priority.
|
||||
- Audio recording by call with new 'Non-Recordable' alias action
|
||||
- (planned)Added Ettus UHD support for B100 with WBX tuner
|
||||
- (planned)Added Airspy tuner support
|
||||
- Added Airspy tuner support
|
||||
|
||||
0.1.5 20150612 Added initial P25 Decoder support with integrated audio decoding
|
||||
using the JMBE library
|
||||
|
||||
@@ -146,8 +146,7 @@ public class HilbertTransform implements Listener<RealBuffer>
|
||||
if( mInvertFlag )
|
||||
{
|
||||
//inphase
|
||||
samples[ y ] = -( mCenterCoefficent *
|
||||
mBuffer[ mIndexMap[ index ][ mCenterTapIndex ] ] );
|
||||
samples[ y ] = -( mBuffer[ mIndexMap[ index ][ mCenterTapIndex ] ] );
|
||||
|
||||
//quadrature
|
||||
samples[ y + 1 ] = -accumulator;
|
||||
@@ -155,8 +154,7 @@ public class HilbertTransform implements Listener<RealBuffer>
|
||||
else
|
||||
{
|
||||
//inphase
|
||||
samples[ y ] = mCenterCoefficent *
|
||||
mBuffer[ mIndexMap[ index ][ mCenterTapIndex ] ];
|
||||
samples[ y ] = mBuffer[ mIndexMap[ index ][ mCenterTapIndex ] ];
|
||||
|
||||
//quadrature
|
||||
samples[ y + 1 ] = accumulator;
|
||||
@@ -255,6 +253,9 @@ public class HilbertTransform implements Listener<RealBuffer>
|
||||
* 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.
|
||||
*
|
||||
* Note: we apply a 2.0 gain to the coefficients to compensate for the loss
|
||||
* of splitting the signal via two filters.
|
||||
*/
|
||||
private void convertHalfBandToHilbert( float[] coefficients )
|
||||
{
|
||||
@@ -266,15 +267,15 @@ public class HilbertTransform implements Listener<RealBuffer>
|
||||
{
|
||||
if( x < middle )
|
||||
{
|
||||
mHilbertFilter[ x ] = -Math.abs( coefficients[ x ] );
|
||||
mHilbertFilter[ x ] = 2.0f * -Math.abs( coefficients[ x ] );
|
||||
}
|
||||
else if( x > middle )
|
||||
{
|
||||
mHilbertFilter[ x ] = Math.abs( coefficients[ x ] );
|
||||
mHilbertFilter[ x ] = 2.0f * Math.abs( coefficients[ x ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
mHilbertFilter[ x ] = coefficients[ x ];
|
||||
mHilbertFilter[ x ] = 2.0f * coefficients[ x ];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,21 @@
|
||||
******************************************************************************/
|
||||
package dsp.mixer;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.jtransforms.fft.FloatFFT_1D;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import dsp.filter.Filters;
|
||||
import dsp.filter.hilbert.HilbertTransform;
|
||||
import sample.complex.Complex;
|
||||
|
||||
public class Oscillator
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( Oscillator.class );
|
||||
|
||||
private double mFrequency;
|
||||
private double mSampleRate;
|
||||
|
||||
@@ -105,4 +116,109 @@ public class Oscillator
|
||||
{
|
||||
return mCurrentAngle.real();
|
||||
}
|
||||
|
||||
public static void main( String[] args )
|
||||
{
|
||||
Oscillator o = new Oscillator( 1536, 4096 );
|
||||
|
||||
o.rotate();
|
||||
|
||||
float[] samples = new float[ 8192 ];
|
||||
|
||||
for( int x = 0; x < 8192; x++ )
|
||||
{
|
||||
samples[ x ] = o.inphase();
|
||||
|
||||
o.rotate();
|
||||
}
|
||||
|
||||
logLargest( "Real: ", samples, false );
|
||||
|
||||
HilbertTransform ht = new HilbertTransform();
|
||||
|
||||
float[] transformed = ht.filter( samples );
|
||||
|
||||
logLargest( "Hilbert: ", transformed, true );
|
||||
|
||||
FloatFFT_1D fft = new FloatFFT_1D( 4096 );
|
||||
|
||||
fft.complexForward( transformed );
|
||||
|
||||
logLargest( "DFT:", transformed, true );
|
||||
}
|
||||
|
||||
private static void logLargest( String label, float[] samples, boolean complex )
|
||||
{
|
||||
float largestI = 0.0f;
|
||||
int indexLargestI = 0;
|
||||
float largestQ = 0.0f;
|
||||
int indexLargestQ = 0;
|
||||
|
||||
float smallestI = 0.0f;
|
||||
int indexsmallestI = 0;
|
||||
float smallestQ = 0.0f;
|
||||
int indexsmallestQ = 0;
|
||||
|
||||
for( int x = 0; x < samples.length; x++ )
|
||||
{
|
||||
if( complex )
|
||||
{
|
||||
if( x % 2 == 0 )
|
||||
{
|
||||
if( samples[ x ] > largestI )
|
||||
{
|
||||
largestI = samples[ x ];
|
||||
indexLargestI = x;
|
||||
}
|
||||
if( samples[ x ] < smallestI )
|
||||
{
|
||||
smallestI = samples[ x ];
|
||||
indexsmallestI = x;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( samples[ x ] > largestQ )
|
||||
{
|
||||
largestQ = samples[ x ];
|
||||
indexLargestQ = x;
|
||||
}
|
||||
if( samples[ x ] < smallestQ )
|
||||
{
|
||||
smallestQ = samples[ x ];
|
||||
indexsmallestQ = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( samples[ x ] > largestI )
|
||||
{
|
||||
largestI = samples[ x ];
|
||||
indexLargestI = x;
|
||||
}
|
||||
if( samples[ x ] < smallestI )
|
||||
{
|
||||
smallestI = samples[ x ];
|
||||
indexsmallestI = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if( complex )
|
||||
{
|
||||
mLog.debug( label +
|
||||
" largestI (" + indexLargestI + "): " + largestI +
|
||||
" smallestI (" + indexsmallestI + "): " + smallestI +
|
||||
" largestQ (" + indexLargestQ + "): " + largestQ +
|
||||
" smallestQ (" + indexsmallestQ + "): " + smallestQ );
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.debug( label +
|
||||
" largest (" + indexLargestI + "): " + largestI +
|
||||
" smallest (" + indexsmallestI + "): " + smallestI );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 source.tuner.airspy;
|
||||
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
@@ -20,23 +37,6 @@ 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 );
|
||||
|
||||
@@ -389,7 +389,6 @@ 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
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.awt.Rectangle;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.geom.GeneralPath;
|
||||
import java.awt.geom.Line2D;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
|
||||
@@ -111,8 +112,8 @@ public class SpectrumPanel extends JPanel
|
||||
public void setSampleSize( double sampleSize )
|
||||
{
|
||||
assert( 2.0 <= sampleSize && sampleSize <= 32.0 );
|
||||
|
||||
mDBScale = -(float)( 20.0 * Math.log10( Math.pow( 2.0, sampleSize ) ) );
|
||||
|
||||
mDBScale = (float)( 20.0 * Math.log10( 1 / Math.pow( 2.0, sampleSize - 1 ) ) );
|
||||
}
|
||||
|
||||
public void setAveraging( int size )
|
||||
|
||||
@@ -33,6 +33,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import settings.ColorSetting;
|
||||
import settings.ColorSetting.ColorSettingName;
|
||||
import settings.Setting;
|
||||
@@ -48,7 +51,10 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener,
|
||||
Pausable,
|
||||
SettingChangeListener
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( WaterfallPanel.class );
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static DecimalFormat CURSOR_FORMAT = new DecimalFormat( "0.00000" );
|
||||
private static final String SPECTRUM_CURSOR = "spectrum_cursor";
|
||||
private static final String PAUSED = "PAUSED";
|
||||
@@ -202,9 +208,9 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener,
|
||||
public void receive( float[] update )
|
||||
{
|
||||
//If our FFT size changes, reset our pixel map and image source
|
||||
if( mFFTSize != update.length - 1 )
|
||||
if( mFFTSize != update.length )
|
||||
{
|
||||
mFFTSize = update.length - 1;
|
||||
mFFTSize = update.length;
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
@@ -20,13 +20,50 @@ package spectrum.converter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Converts complex DFT output to scaled dB values with a maximum amplitude of
|
||||
* 0 dB and all values scaled to the minimum dB value which is:
|
||||
*
|
||||
* Max: 0dB
|
||||
* Min: 20 * log10( 1.0 / ( 2.0 ^ ( bit depth - 1) ) = scaling factor
|
||||
*
|
||||
* Notes:
|
||||
*
|
||||
* The magnitude of each DFT output bin must be scaled by the number of DFT
|
||||
* points to normalize the output to a range of ( 0 to 1.0 ). DFT bin magnitude
|
||||
* can be calculated as:
|
||||
*
|
||||
* 1) sqrt( bin[ x ] * bin[ x ] + bin[ x + 1 ] * bin[ x + 1] ) / DFTSize or,
|
||||
* 2) ( bin[ x ] * bin[ x ] + bin[ x + 1 ] * bin[ x + 1] ) / ( DFTSize * DFTSize )
|
||||
*
|
||||
* Convert each scaled DFT bin magnitude to decibels using this formula:
|
||||
*
|
||||
* 20 * log10( scaledBinValue )
|
||||
*
|
||||
* Max Value: 20 * log10( 1.0 ) = 0 dB
|
||||
*
|
||||
* When plotting, these dB bin values should be scaled according to the maximum
|
||||
* dynamic range supported by the source. Maximum dynamic range for bit depth
|
||||
* is calculated as:
|
||||
*
|
||||
* 20 * log10( 1 / ( 2 ^ ( bit depth - 1 ) )
|
||||
*
|
||||
* A source that provides 12-bit samples can produce values in the range of
|
||||
* ( 0 to 4096 ) or -2048 to 2047. The smallest observable value would then
|
||||
* be 1 / 2048.
|
||||
*
|
||||
* So, the smallest decibel value of a source producing 12 bit samples is:
|
||||
*
|
||||
* 20 * log10( 1 / 2048 ) = -66.23 dB
|
||||
*
|
||||
* and the dynamic range is: (-66.23 to 0.0 dB)
|
||||
*
|
||||
*/
|
||||
public class ComplexDecibelConverter extends DFTResultsConverter
|
||||
{
|
||||
private final static Logger mLog =
|
||||
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 the power spectrum in decibels, normalized to the
|
||||
@@ -36,44 +73,35 @@ public class ComplexDecibelConverter extends DFTResultsConverter
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ];
|
||||
|
||||
int half = processed.length / 2;
|
||||
int halfResults = results.length / 2;
|
||||
|
||||
float dftScalor = 1.0f / (float)( halfResults ^ 2 );
|
||||
|
||||
float[] processed = new float[ halfResults ];
|
||||
|
||||
int middle = processed.length / 2;
|
||||
|
||||
for( int x = 0; x < results.length; x += 2 )
|
||||
{
|
||||
//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(
|
||||
//Calculate the magnitude squared (power) value from each bin's real
|
||||
//and imaginary value and scale it to the DFT bin size squared.
|
||||
//Convert the scaled value to decibels.
|
||||
float decibels = 10.0f * (float)Math.log10(
|
||||
( ( results[ x ] * results[ x ] ) +
|
||||
( results[ x + 1 ] * results[ x + 1 ] ) ) /
|
||||
mDynamicRangeReference );
|
||||
( results[ x + 1 ] * results[ x + 1 ] ) ) * dftScalor );
|
||||
|
||||
// We have to swap the upper and lower halves of the JTransforms
|
||||
// DFT results for correct display
|
||||
if( x / 2 >= half )
|
||||
if( x / 2 >= middle )
|
||||
{
|
||||
processed[ x / 2 - half ] = normalizedMagnitude;
|
||||
processed[ x / 2 - middle ] = decibels;
|
||||
}
|
||||
else
|
||||
{
|
||||
processed[ x / 2 + half ] = normalizedMagnitude;
|
||||
processed[ x / 2 + middle ] = decibels;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,12 +19,6 @@ public abstract class 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()
|
||||
{
|
||||
mListeners.clear();
|
||||
|
||||
@@ -7,33 +7,22 @@ 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
|
||||
* in Lyons, Understanding Digital Signal Processing, 3e, page 141.
|
||||
*
|
||||
* Note: this is only calculating the lower half of the spectrum
|
||||
*/
|
||||
@Override
|
||||
public void receive( float[] results )
|
||||
{
|
||||
float dftScalor = 1.0f / (float)( results.length ^ 2 );
|
||||
|
||||
float[] processed = new float[ results.length / 4 ];
|
||||
|
||||
int index = 0;
|
||||
@@ -42,12 +31,9 @@ public class RealDecibelConverter extends DFTResultsConverter
|
||||
{
|
||||
index = x * 2;
|
||||
|
||||
float normalizedMagnitude = 10.0f * (float)Math.log10(
|
||||
processed[ x ] = 20.0f * (float)Math.log10(
|
||||
( ( results[ index ] * results[ index ] ) +
|
||||
( results[ index + 1 ] * results[ index + 1 ] ) ) /
|
||||
mDynamicRangeReference );
|
||||
|
||||
processed[ x ] = normalizedMagnitude;
|
||||
( results[ index + 1 ] * results[ index + 1 ] ) ) * dftScalor );
|
||||
}
|
||||
|
||||
dispatch( processed );
|
||||
|
||||
Reference in New Issue
Block a user