[WIP] Removes all usage of Buffer, RealBuffer and ComplexBuffer.

This commit is contained in:
Denny
2018-06-30 06:54:36 -04:00
parent 6f1ce79461
commit ad560db5ea
50 changed files with 1224 additions and 2431 deletions

View File

@@ -32,8 +32,8 @@ import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.event.CallEvent;
import io.github.dsheirer.module.decode.event.ICallEventProvider;
import io.github.dsheirer.sample.IOverflowListener;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.real.IOverflowListener;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.heartbeat.Heartbeat;

View File

@@ -1,69 +0,0 @@
/*******************************************************************************
* 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/>
******************************************************************************/
package io.github.dsheirer.dsp.am;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.sample.real.RealBuffer;
public class AMDemodulator_CB extends AMDemodulator implements Listener<ComplexBuffer>
{
private Listener<RealBuffer> mListener;
public AMDemodulator_CB( float gain )
{
super( gain );
}
@Override
public void receive( ComplexBuffer buffer )
{
if( mListener != null )
{
float[] samples = buffer.getSamples();
int half = samples.length / 2;
float[] demodulated = new float[ half ];
for( int x = 0; x < half; x++ )
{
int index = x * 2;
demodulated[ x ] = demodulate( samples[ index ], samples[ index + 1 ] );
}
mListener.receive( new RealBuffer( demodulated ) );
}
}
public void setListener( Listener<RealBuffer> listener )
{
mListener = listener;
}
public void removeListener()
{
mListener = null;
}
public void dispose()
{
mListener = null;
}
}

View File

@@ -20,10 +20,10 @@ package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.sample.IOverflowListener;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.real.IOverflowListener;
import org.jtransforms.fft.FloatFFT_1D;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -15,9 +15,9 @@
******************************************************************************/
package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.sample.IOverflowListener;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.OverflowableTransferQueue;
import io.github.dsheirer.sample.real.IOverflowListener;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -17,9 +17,9 @@ package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableBufferAssembler;
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
@@ -30,7 +30,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
{
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class);
private ReusableBufferAssembler mReusableBufferAssembler;
private ReusableComplexBufferAssembler mReusableComplexBufferAssembler;
private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor;
private double mChannelSampleRate;
private long mIndexCenterFrequency;
@@ -54,7 +54,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
super(producerSourceEventListener, tunerChannel);
mPolyphaseChannelOutputProcessor = outputProcessor;
mChannelSampleRate = channelSampleRate;
mReusableBufferAssembler = new ReusableBufferAssembler(2500, mChannelSampleRate);
mReusableComplexBufferAssembler = new ReusableComplexBufferAssembler(2500, mChannelSampleRate);
setFrequency(centerFrequency);
}
@@ -64,7 +64,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
@Override
public void setListener(final Listener<ReusableComplexBuffer> listener)
{
mReusableBufferAssembler.setListener(listener);
mReusableComplexBufferAssembler.setListener(listener);
}
/**
@@ -73,7 +73,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
@Override
public void removeListener(Listener<ReusableComplexBuffer> listener)
{
mReusableBufferAssembler.setListener(null);
mReusableComplexBufferAssembler.setListener(null);
}
/**
@@ -112,7 +112,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
//Fully process the residual channel results buffer of the previous channel output processor
if(existingProcessor != null)
{
existingProcessor.processChannelResults(mReusableBufferAssembler);
existingProcessor.processChannelResults(mReusableComplexBufferAssembler);
}
//Finally, setup the frequency offset for the output processor and reset the frequency correction value
@@ -133,7 +133,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
*/
public void receiveChannelResults(ReusableChannelResultsBuffer channelResultsBuffer)
{
mReusableBufferAssembler.updateTimestamp(channelResultsBuffer.getTimestamp());
mReusableComplexBufferAssembler.updateTimestamp(channelResultsBuffer.getTimestamp());
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsBuffer);
}
@@ -161,9 +161,9 @@ public class PolyphaseChannelSource extends TunerChannelSource
@Override
public void dispose()
{
if(mReusableBufferAssembler != null)
if(mReusableComplexBufferAssembler != null)
{
mReusableBufferAssembler.dispose();
mReusableComplexBufferAssembler.dispose();
}
if(mPolyphaseChannelOutputProcessor != null)
@@ -212,7 +212,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
//Lock on the output processor so that it can't be changed in the middle of processing
synchronized(mPolyphaseChannelOutputProcessor)
{
mPolyphaseChannelOutputProcessor.processChannelResults(mReusableBufferAssembler);
mPolyphaseChannelOutputProcessor.processChannelResults(mReusableComplexBufferAssembler);
}
}

View File

@@ -20,10 +20,10 @@ package io.github.dsheirer.dsp.filter.channelizer.output;
import io.github.dsheirer.dsp.mixer.IOscillator;
import io.github.dsheirer.dsp.mixer.Oscillator;
import io.github.dsheirer.sample.IOverflowListener;
import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue;
import io.github.dsheirer.sample.buffer.ReusableBufferAssembler;
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
import io.github.dsheirer.sample.real.IOverflowListener;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
import io.github.dsheirer.source.Source;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -99,10 +99,10 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
/**
* Processes all enqueued polyphase channelizer results until the internal queue is empty
* @param reusableBufferAssembler to receive the processed channel results
* @param reusableComplexBufferAssembler to receive the processed channel results
*/
@Override
public void processChannelResults(ReusableBufferAssembler reusableBufferAssembler)
public void processChannelResults(ReusableComplexBufferAssembler reusableComplexBufferAssembler)
{
try
{
@@ -110,7 +110,7 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
if(toProcess > 0)
{
process(mChannelResultsToProcess, reusableBufferAssembler);
process(mChannelResultsToProcess, reusableComplexBufferAssembler);
}
}
catch(Throwable throwable)
@@ -124,10 +124,10 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
/**
* Sub-class implementation to process one polyphase channelizer result array.
* @param channelResults to process
* @param reusableBufferAssembler to receive the processed channelizer results
* @param reusableComplexBufferAssembler to receive the processed channelizer results
*/
public abstract void process(List<ReusableChannelResultsBuffer> channelResults,
ReusableBufferAssembler reusableBufferAssembler);
ReusableComplexBufferAssembler reusableComplexBufferAssembler);
/**

View File

@@ -18,8 +18,8 @@
******************************************************************************/
package io.github.dsheirer.dsp.filter.channelizer.output;
import io.github.dsheirer.sample.buffer.ReusableBufferAssembler;
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
import io.github.dsheirer.source.Source;
import java.util.List;
@@ -35,9 +35,9 @@ public interface IPolyphaseChannelOutputProcessor
/**
* Process the channel output channel results queue and deliver the output to the listener
*
* @param reusableBufferAssembler to receive the processed channel results
* @param reusableComplexBufferAssembler to receive the processed channel results
*/
void processChannelResults(ReusableBufferAssembler reusableBufferAssembler);
void processChannelResults(ReusableComplexBufferAssembler reusableComplexBufferAssembler);
/**
* Sets the desired frequency offset from center. The samples will be mixed with an oscillator set to this offset

View File

@@ -18,9 +18,9 @@
******************************************************************************/
package io.github.dsheirer.dsp.filter.channelizer.output;
import io.github.dsheirer.sample.buffer.ReusableBufferAssembler;
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -74,11 +74,11 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
* extracted frequency-corrected channel I/Q sample set to the complex sample listener.
*
* @param channelResults to process containing a list of channel array of I/Q sample pairs (I0,Q0,I1,Q1...In,Qn)
* @param reusableBufferAssembler to receive the extracted, frequency-translated channel results
* @param reusableComplexBufferAssembler to receive the extracted, frequency-translated channel results
*/
@Override
public void process(List<ReusableChannelResultsBuffer> channelResults,
ReusableBufferAssembler reusableBufferAssembler)
ReusableComplexBufferAssembler reusableComplexBufferAssembler)
{
for(ReusableChannelResultsBuffer channelResultsBuffer: channelResults)
{
@@ -91,7 +91,7 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
getFrequencyCorrectionMixer().mixComplex(channelBuffer.getSamples());
}
reusableBufferAssembler.receive(channelBuffer);
reusableComplexBufferAssembler.receive(channelBuffer);
}
catch(IllegalArgumentException iae)
{

View File

@@ -21,9 +21,9 @@ import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
import io.github.dsheirer.dsp.mixer.FS4DownConverter;
import io.github.dsheirer.sample.buffer.ReusableBufferAssembler;
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -119,11 +119,11 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
* extracted frequency-corrected channel I/Q sample set to the complex sample listener.
*
* @param channelResultsBuffers to process containing an array of channel I/Q sample pairs (I0,Q0,I1,Q1...In,Qn)
* @param reusableBufferAssembler to receive the extracted, frequency-translated channel results
* @param reusableComplexBufferAssembler to receive the extracted, frequency-translated channel results
*/
@Override
public void process(List<ReusableChannelResultsBuffer> channelResultsBuffers,
ReusableBufferAssembler reusableBufferAssembler)
ReusableComplexBufferAssembler reusableComplexBufferAssembler)
{
for(ReusableChannelResultsBuffer buffer: channelResultsBuffers)
{
@@ -141,7 +141,7 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
ReusableComplexBuffer lowPassFiltered = mLowPassFilter.filter(synthesized);
reusableBufferAssembler.receive(lowPassFiltered);
reusableComplexBufferAssembler.receive(lowPassFiltered);
buffer.decrementUserCount();
}

View File

@@ -1,19 +1,16 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
* sdr-trunk
* Copyright (C) 2014-2018 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 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.
* 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/>
* 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 io.github.dsheirer.dsp.filter.cic;
@@ -21,19 +18,19 @@ package io.github.dsheirer.dsp.filter.cic;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Filters;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
import io.github.dsheirer.dsp.filter.halfband.complex.HalfBandFilter_CB_CB;
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2;
import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandFilter;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
import io.github.dsheirer.sample.complex.ComplexSampleListener;
import io.github.dsheirer.sample.complex.ComplexToComplexBufferAssembler;
import io.github.dsheirer.sample.decimator.ComplexDecimator;
import org.apache.commons.lang3.Validate;
import java.util.ArrayList;
import java.util.List;
public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
{
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,
@@ -87,7 +84,7 @@ public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
}
else
{
/* Wire the current stage to the previous stage */
/* Wire the current stage to the previous stage */
mDecimatingStages.get(x - 1).setListener(stage);
}
}
@@ -116,7 +113,7 @@ public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
/**
* Adds a listener to receive the output of this CIC decimation filter
*/
public void setListener(Listener<ComplexBuffer> listener)
public void setListener(Listener<ReusableComplexBuffer> listener)
{
mOutput.setListener(listener);
}
@@ -166,7 +163,7 @@ public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
* Primary input method for receiving sample arrays composed as I,Q,I,Q, etc.
*/
@Override
public void receive(ComplexBuffer buffer)
public void receive(ReusableComplexBuffer buffer)
{
if(mFirstDecimatingStage != null)
{
@@ -177,6 +174,8 @@ public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
mFirstDecimatingStage.receive(samples[x], samples[x + 1]);
}
}
buffer.decrementUserCount();
}
/**
@@ -289,11 +288,11 @@ public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
public void receive(float i, float q)
{
/* Subtract the oldest sample and add back in the newest */
/* Subtract the oldest sample and add back in the newest */
mISum = mISum - mISamples[mSamplePointer] + i;
mQSum = mQSum - mQSamples[mSamplePointer] + q;
/* Overwrite the oldest sample with the newest */
/* Overwrite the oldest sample with the newest */
mISamples[mSamplePointer] = i;
mQSamples[mSamplePointer] = q;
@@ -352,27 +351,37 @@ public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
public class Output implements ComplexSampleListener
{
/* Decimated output buffers will contain 1024 samples */
private ComplexToComplexBufferAssembler mAssembler =
new ComplexToComplexBufferAssembler(2048);
private ReusableComplexBufferAssembler mAssembler = new ReusableComplexBufferAssembler(2048, 48000.0);
private ComplexFIRFilter_CB_CB mCleanupFilter;
private HalfBandFilter_CB_CB mHalfBandFilter = new HalfBandFilter_CB_CB(
private ComplexFIRFilter2 mCleanupFilter;
private ComplexHalfBandFilter mHalfBandFilter = new ComplexHalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false);
private Listener<ReusableComplexBuffer> mReusableComplexBufferListener;
public Output(int outputSampleRate, int passFrequency, int attenuation,
Window.WindowType windowType)
{
mCleanupFilter = new ComplexFIRFilter_CB_CB(FilterFactory
.getCICCleanupFilter(outputSampleRate,
passFrequency,
attenuation,
windowType), 0.4f);
mCleanupFilter = new ComplexFIRFilter2(FilterFactory
.getCICCleanupFilter(outputSampleRate, passFrequency, attenuation, windowType), 0.4f);
//Bypassing the CIC cleanup filter for now
mAssembler.setListener(mHalfBandFilter);
// mAssembler.setListener( mCleanupFilter );
// mCleanupFilter.setListener( mHalfBandFilter );
mAssembler.setListener(new Listener<ReusableComplexBuffer>()
{
@Override
public void receive(ReusableComplexBuffer reusableComplexBuffer)
{
if(mReusableComplexBufferListener != null)
{
ReusableComplexBuffer cleanupFiltered = mCleanupFilter.filter(reusableComplexBuffer);
ReusableComplexBuffer halfBandFiltered = mHalfBandFilter.filter(cleanupFiltered);
mReusableComplexBufferListener.receive(halfBandFiltered);
}
else
{
reusableComplexBuffer.decrementUserCount();
}
}
});
}
public void dispose()
@@ -390,23 +399,17 @@ public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
@Override
public void receive(float inphase, float quadrature)
{
mAssembler.receive(inphase, quadrature);
mAssembler.receive(new float[]{inphase, quadrature});
}
/**
* Adds a listener to receive output samples
*/
public void setListener(Listener<ComplexBuffer> listener)
public void setListener(Listener<ReusableComplexBuffer> listener)
{
mHalfBandFilter.setListener(listener);
mReusableComplexBufferListener = listener;
}
/**
* Removes the listener from receiving output samples
*/
public void removeListener()
{
mHalfBandFilter.removeListener();
mReusableComplexBufferListener = null;
}
}
}

View File

@@ -1,17 +1,17 @@
/*******************************************************************************
* 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/>
******************************************************************************/
@@ -20,13 +20,13 @@ package io.github.dsheirer.dsp.filter.cic;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Filters;
import io.github.dsheirer.dsp.filter.Window.WindowType;
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter_RB_RB;
import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter_RB_RB;
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2;
import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferAssembler;
import io.github.dsheirer.sample.decimator.RealDecimator;
import io.github.dsheirer.sample.real.RealBuffer;
import io.github.dsheirer.sample.real.RealSampleListener;
import io.github.dsheirer.sample.real.RealToRealBufferAssembler;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,381 +34,389 @@ import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
public class RealPrimeCICDecimate
public class RealPrimeCICDecimate
{
private final static Logger mLog =
LoggerFactory.getLogger( RealPrimeCICDecimate.class );
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,
157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,
257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,
367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,
467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,
599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691 };
private ArrayList<DecimatingStage> mDecimatingStages =
new ArrayList<DecimatingStage>();
private DecimatingStage mFirstDecimatingStage;
private final static Logger mLog = LoggerFactory.getLogger(RealPrimeCICDecimate.class);
private Output mOutput;
private int mOutputBufferSize = 2048;
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,
157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251,
257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359,
367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463,
467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593,
599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691};
/**
* Non-Recursive Prime-Factor CIC Filter with float sample array inputs and
* decimated float sample output.
*
* Implements the CIC filter described in Understanding Digital Signal
* Processing, 3e, Lyons, on page 769. This filter is comprised of multiple
* decimating stages each with a prime factor decimation rate. Multiple
* stages are cascaded to achieve the overall decimation rate.
*
* This filter supports a maximum decimation rate of 700. This filter can
* be adapted to higher decimation rates by adding additional prime factors
* to the PRIMES array.
*
* @param decimation - overall decimation rate
* @param order - filter order
*/
public RealPrimeCICDecimate( int decimation, int order, int passFrequency,
int attenuation, WindowType windowType )
{
this( decimation, order, passFrequency, attenuation, windowType, 2048 );
}
public RealPrimeCICDecimate( int decimation, int order, int passFrequency,
int attenuation, WindowType windowType, int outputBufferSize )
{
Validate.isTrue(decimation <= 700);
private List<DecimatingStage> mDecimatingStages = new ArrayList<DecimatingStage>();
private DecimatingStage mFirstDecimatingStage;
private Output mOutput;
mOutputBufferSize = outputBufferSize;
private int mOutputBufferSize = 2048;
List<Integer> stageSizes = getPrimeFactors( decimation );
for( int x = 0; x < stageSizes.size(); x++ )
{
DecimatingStage stage = new DecimatingStage( stageSizes.get( x ), order );
mDecimatingStages.add( stage );
if( x == 0 )
{
/* Reference to first stage -- will receive all samples */
mFirstDecimatingStage = stage;
}
else
{
/* Wire the current stage to the previous stage */
mDecimatingStages.get( x - 1 ).setListener( stage );
}
}
mOutput = new Output( 48000, passFrequency, attenuation, windowType,
outputBufferSize );
mDecimatingStages.get( mDecimatingStages.size() - 1 )
.setListener( mOutput );
}
public void dispose()
{
for( DecimatingStage stage: mDecimatingStages )
{
stage.dispose();
}
mDecimatingStages.clear();
mDecimatingStages = null;
mFirstDecimatingStage = null;
mOutput.dispose();
mOutput = null;
}
/**
* Adds a listener to receive the output of this CIC decimation filter
*/
public void setListener( Listener<RealBuffer> listener )
{
mOutput.setListener( listener );
}
/**
* Removes listener from output of this CIC decimation filter
*/
public void removeListener()
{
mOutput.removeListener();
}
/**
* Calculates the prime factors of the decimation rate up to a maximum
* decimation rate of 700. If you wish to have decimation rates higher
* than 700, then add additional prime factors to the PRIMES array.
*
* @param decimation - integral decimation rate
* @return - ordered list (smallest to largest) of prime factors
*/
public static List<Integer> getPrimeFactors( int decimation )
{
ArrayList<Integer> stages = new ArrayList<Integer>();
int pointer = 0;
while( decimation > 0 && pointer < PRIMES.length )
{
int prime = PRIMES[ pointer ];
if( decimation % prime == 0 )
{
stages.add( prime );
decimation /= prime;
}
else
{
pointer++;
}
}
return stages;
}
/**
* Primary input method for receiving real buffers.
*/
public void receive( RealBuffer buffer )
/**
* Non-Recursive Prime-Factor CIC Filter with float sample array inputs and
* decimated float sample output.
*
* Implements the CIC filter described in Understanding Digital Signal
* Processing, 3e, Lyons, on page 769. This filter is comprised of multiple
* decimating stages each with a prime factor decimation rate. Multiple
* stages are cascaded to achieve the overall decimation rate.
*
* This filter supports a maximum decimation rate of 700. This filter can
* be adapted to higher decimation rates by adding additional prime factors
* to the PRIMES array.
*
* @param decimation - overall decimation rate
* @param order - filter order
*/
public RealPrimeCICDecimate(int decimation, int order, int passFrequency, int attenuation, WindowType windowType)
{
if( mFirstDecimatingStage != null )
{
for( float sample: buffer.getSamples() )
{
mFirstDecimatingStage.receive( sample );
}
}
this(decimation, order, passFrequency, attenuation, windowType, 2048);
}
/**
* Decimating stage combines multiple CIC stages with a decimator. The
* number of stages is indicated by the order value and the size indicates
* the decimation rate of this stage.
*/
public class DecimatingStage implements RealSampleListener
{
private ArrayList<Stage> mStages = new ArrayList<Stage>();
private Stage mFirstStage;
private RealDecimator mDecimator;
public DecimatingStage( int size, int order )
{
for( int x = 0; x < order; x++ )
{
Stage stage;
if( size == 2 )
{
stage = new TwoStage();
}
else
{
stage = new Stage( size );
}
mStages.add( stage );
if( x == 0 )
{
mFirstStage = stage;
}
else
{
mStages.get( x - 1 ).setListener( stage );
}
}
mDecimator = new RealDecimator( size );
mStages.get( mStages.size() - 1 ).setListener( mDecimator );
}
public void dispose()
{
for( Stage stage: mStages )
{
stage.dispose();
}
mStages.clear();
mDecimator.dispose();
mDecimator = null;
mFirstStage = null;
mStages = null;
}
@Override
public void receive( float sample )
public RealPrimeCICDecimate(int decimation, int order, int passFrequency, int attenuation, WindowType windowType, int outputBufferSize)
{
Validate.isTrue(decimation <= 700);
mOutputBufferSize = outputBufferSize;
List<Integer> stageSizes = getPrimeFactors(decimation);
for(int x = 0; x < stageSizes.size(); x++)
{
mFirstStage.receive( sample );
}
public void setListener( RealSampleListener listener )
{
mDecimator.setListener( listener );
}
}
/**
* Single non-decimating CIC stage component. Uses a circular buffer and a
* running average internally to implement the stage so that stage size has
* essentially no impact on the computational requirements of the stage
*/
public class Stage implements RealSampleListener
{
protected RealSampleListener mListener;
private float[] mSamples;
protected float mSum;
private int mSamplePointer = 0;
private int mSize;
protected float mGain;
public Stage()
{
}
DecimatingStage stage = new DecimatingStage(stageSizes.get(x), order);
public Stage( int size )
{
mSize = size - 1;
mSamples = new float[ mSize ];
mGain = 1.0f / (float)size;
}
public void dispose()
{
mListener = null;
}
public void receive( float sample )
{
/* Subtract the oldest sample and add back in the newest */
mSum = mSum - mSamples[ mSamplePointer ] + sample;
mDecimatingStages.add(stage);
/* Overwrite the oldest sample with the newest */
mSamples[ mSamplePointer ] = sample;
mSamplePointer++;
if( mSamplePointer >= mSize )
{
mSamplePointer = 0;
}
if( mListener != null )
{
mListener.receive( mSum * mGain );
}
}
public void setListener( RealSampleListener listener )
{
mListener = listener;
}
}
/**
* Size 2 stage that removes the unnecessary circular buffer management for
* a two-stage.
*/
public class TwoStage extends Stage
{
public TwoStage()
{
super();
mGain = 0.5f;
}
public void receive( float sample )
{
float sum = mSum + sample;
mSum = sample;
if( mListener != null )
{
mListener.receive( sum * mGain );
}
}
}
/**
* 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.
*/
public class Output implements RealSampleListener
{
/* Decimated output buffers will contain 1024 samples */
private RealToRealBufferAssembler mAssembler;
private RealFIRFilter_RB_RB mCleanupFilter;
private HalfBandFilter_RB_RB mHalfBandFilter = new HalfBandFilter_RB_RB(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false );
public Output( int outputSampleRate, int passFrequency, int attenuation,
WindowType windowType, int outputBufferSize )
{
mCleanupFilter = new RealFIRFilter_RB_RB( FilterFactory
.getCICCleanupFilter( outputSampleRate,
passFrequency,
attenuation,
windowType ), 0.4f );
mAssembler = new RealToRealBufferAssembler( outputBufferSize );
mAssembler.setListener( mCleanupFilter );
mCleanupFilter.setListener( mHalfBandFilter );
}
public void dispose()
{
mAssembler.dispose();
mCleanupFilter.dispose();
mHalfBandFilter.dispose();
}
/**
* Receiver method for the output adapter to receive a filtered,
* decimated sample, apply gain correction, apply cleanup filtering
* and output the sample values as a complex sample.
*/
@Override
public void receive( float sample )
{
mAssembler.receive( sample * 32.0f );
}
/**
* Adds a listener to receive output samples
*/
public void setListener( Listener<RealBuffer> listener )
{
mHalfBandFilter.setListener( listener );
if(x == 0)
{
/* Reference to first stage -- will receive all samples */
mFirstDecimatingStage = stage;
}
else
{
/* Wire the current stage to the previous stage */
mDecimatingStages.get(x - 1).setListener(stage);
}
}
/**
* Removes the listener from receiving output samples
*/
mOutput = new Output(48000, passFrequency, attenuation, windowType,
outputBufferSize);
mDecimatingStages.get(mDecimatingStages.size() - 1)
.setListener(mOutput);
}
public void dispose()
{
for(DecimatingStage stage : mDecimatingStages)
{
stage.dispose();
}
mDecimatingStages.clear();
mDecimatingStages = null;
mFirstDecimatingStage = null;
mOutput.dispose();
mOutput = null;
}
/**
* Adds a listener to receive the output of this CIC decimation filter
*/
public void setListener(Listener<ReusableBuffer> listener)
{
mOutput.setListener(listener);
}
/**
* Removes listener from output of this CIC decimation filter
*/
public void removeListener()
{
mOutput.removeListener();
}
/**
* Calculates the prime factors of the decimation rate up to a maximum
* decimation rate of 700. If you wish to have decimation rates higher
* than 700, then add additional prime factors to the PRIMES array.
*
* @param decimation - integral decimation rate
* @return - ordered list (smallest to largest) of prime factors
*/
public static List<Integer> getPrimeFactors(int decimation)
{
ArrayList<Integer> stages = new ArrayList<Integer>();
int pointer = 0;
while(decimation > 0 && pointer < PRIMES.length)
{
int prime = PRIMES[pointer];
if(decimation % prime == 0)
{
stages.add(prime);
decimation /= prime;
}
else
{
pointer++;
}
}
return stages;
}
/**
* Primary input method for receiving real buffers.
*/
public void receive(ReusableBuffer buffer)
{
if(mFirstDecimatingStage != null)
{
for(float sample : buffer.getSamples())
{
mFirstDecimatingStage.receive(sample);
}
}
buffer.decrementUserCount();
}
/**
* Decimating stage combines multiple CIC stages with a decimator. The
* number of stages is indicated by the order value and the size indicates
* the decimation rate of this stage.
*/
public class DecimatingStage implements RealSampleListener
{
private ArrayList<Stage> mStages = new ArrayList<Stage>();
private Stage mFirstStage;
private RealDecimator mDecimator;
public DecimatingStage(int size, int order)
{
for(int x = 0; x < order; x++)
{
Stage stage;
if(size == 2)
{
stage = new TwoStage();
}
else
{
stage = new Stage(size);
}
mStages.add(stage);
if(x == 0)
{
mFirstStage = stage;
}
else
{
mStages.get(x - 1).setListener(stage);
}
}
mDecimator = new RealDecimator(size);
mStages.get(mStages.size() - 1).setListener(mDecimator);
}
public void dispose()
{
for(Stage stage : mStages)
{
stage.dispose();
}
mStages.clear();
mDecimator.dispose();
mDecimator = null;
mFirstStage = null;
mStages = null;
}
@Override
public void receive(float sample)
{
mFirstStage.receive(sample);
}
public void setListener(RealSampleListener listener)
{
mDecimator.setListener(listener);
}
}
/**
* Single non-decimating CIC stage component. Uses a circular buffer and a
* running average internally to implement the stage so that stage size has
* essentially no impact on the computational requirements of the stage
*/
public class Stage implements RealSampleListener
{
protected RealSampleListener mListener;
private float[] mSamples;
protected float mSum;
private int mSamplePointer = 0;
private int mSize;
protected float mGain;
public Stage()
{
}
public Stage(int size)
{
mSize = size - 1;
mSamples = new float[mSize];
mGain = 1.0f / (float)size;
}
public void dispose()
{
mListener = null;
}
public void receive(float sample)
{
/* Subtract the oldest sample and add back in the newest */
mSum = mSum - mSamples[mSamplePointer] + sample;
/* Overwrite the oldest sample with the newest */
mSamples[mSamplePointer] = sample;
mSamplePointer++;
if(mSamplePointer >= mSize)
{
mSamplePointer = 0;
}
if(mListener != null)
{
mListener.receive(mSum * mGain);
}
}
public void setListener(RealSampleListener listener)
{
mListener = listener;
}
}
/**
* Size 2 stage that removes the unnecessary circular buffer management for
* a two-stage.
*/
public class TwoStage extends Stage
{
public TwoStage()
{
super();
mGain = 0.5f;
}
public void receive(float sample)
{
float sum = mSum + sample;
mSum = sample;
if(mListener != null)
{
mListener.receive(sum * mGain);
}
}
}
/**
* 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.
*/
public class Output implements RealSampleListener
{
/* Decimated output buffers will contain 1024 samples */
private ReusableBufferAssembler mAssembler;
private Listener<ReusableBuffer> mReusableBufferListener;
private RealFIRFilter2 mCleanupFilter;
private HalfBandFilter mHalfBandFilter = new HalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false);
public Output(int outputSampleRate, int passFrequency, int attenuation, WindowType windowType, int outputBufferSize)
{
mCleanupFilter = new RealFIRFilter2(FilterFactory.getCICCleanupFilter(outputSampleRate, passFrequency,
attenuation, windowType), 0.4f);
mAssembler = new ReusableBufferAssembler(outputBufferSize);
mAssembler.setListener(new Listener<ReusableBuffer>()
{
@Override
public void receive(ReusableBuffer reusableBuffer)
{
if(mReusableBufferListener != null)
{
ReusableBuffer cleanupFiltered = mCleanupFilter.filter(reusableBuffer);
ReusableBuffer halfBandFiltered = mHalfBandFilter.filter(cleanupFiltered);
mReusableBufferListener.receive(halfBandFiltered);
}
else
{
reusableBuffer.decrementUserCount();
}
}
});
}
public void dispose()
{
mAssembler.dispose();
mCleanupFilter.dispose();
mHalfBandFilter.dispose();
}
/**
* Receiver method for the output adapter to receive a filtered,
* decimated sample, apply gain correction, apply cleanup filtering
* and output the sample values as a complex sample.
*/
@Override
public void receive(float sample)
{
mAssembler.receive(sample * 32.0f);
}
/**
* Adds a listener to receive output samples
*/
public void setListener(Listener<ReusableBuffer> listener)
{
mReusableBufferListener = listener;
}
/**
* Removes the listener from receiving output samples
*/
public void removeListener()
{
mHalfBandFilter.removeListener();
mReusableBufferListener = null;
}
}
}
}

View File

@@ -18,13 +18,15 @@
******************************************************************************/
package io.github.dsheirer.dsp.filter.correction;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
public class IQCorrectionFilter
{
private float mRatio = 0.00001f;
private float mAverageInphase = 0.0f;
private float mAverageQuadrature = 0.0f;
private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("IQCorrectionFilter");
public IQCorrectionFilter(float ratio)
{
@@ -35,17 +37,23 @@ public class IQCorrectionFilter
{
}
public void filter(ComplexBuffer buffer)
public ReusableComplexBuffer filter(ReusableComplexBuffer buffer)
{
float[] samples = buffer.getSamples();
ReusableComplexBuffer filtered = mReusableComplexBufferQueue.getBuffer(samples.length);
filtered.incrementUserCount();
float[] filteredSamples = filtered.getSamples();
for(int x = 0; x < samples.length; x += 2)
{
mAverageInphase = mAverageInphase + (mRatio * (samples[x] - mAverageInphase));
samples[x] -= mAverageInphase;
filteredSamples[x] = samples[x] - mAverageInphase;
mAverageQuadrature = mAverageQuadrature + (mRatio * (samples[x + 1] - mAverageQuadrature));
samples[x + 1] -= mAverageQuadrature;
filteredSamples[x + 1] = samples[x + 1] - mAverageQuadrature;
}
return filtered;
}
}

View File

@@ -1,129 +0,0 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.filter.fir.complex;
import io.github.dsheirer.dsp.filter.fir.FIRFilter;
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
import io.github.dsheirer.sample.complex.Complex;
@Deprecated //Use ComplexFIRFilter2 instead
public class ComplexFIRFilter extends FIRFilter
{
private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("Complex FIR Filter");
private RealFIRFilter mIFilter;
private RealFIRFilter mQFilter;
private float[] mTemporaryBuffer;
/**
* Complex FIR Filter for processing complex sample pairs. Wraps two real
* FIR filters for processing each of the inphase and quadrature samples.
*
* @param coefficients - filter taps
* @param gain - gain to apply to filtered outputs - use 1.0f for no gain
*/
public ComplexFIRFilter(float[] coefficients, float gain)
{
mIFilter = new RealFIRFilter(coefficients, gain);
mQFilter = new RealFIRFilter(coefficients, gain);
}
public float[] getCoefficients()
{
return mIFilter.getCoefficients();
}
public float filterInphase(float sample)
{
return mIFilter.filter(sample);
}
public float currentInphaseValue()
{
return mIFilter.currentValue();
}
public float filterQuadrature(float sample)
{
return mQFilter.filter(sample);
}
public float currentQuadratureValue()
{
return mQFilter.currentValue();
}
public Complex filter(Complex sample)
{
float i = filterInphase(sample.inphase());
float q = filterQuadrature(sample.quadrature());
return new Complex(i, q);
}
public float[] filter(float[] samples)
{
if(mTemporaryBuffer == null || mTemporaryBuffer.length != samples.length)
{
mTemporaryBuffer = new float[samples.length];
}
System.arraycopy(samples, 0, mTemporaryBuffer, 0, samples.length);
for(int x = 0; x < samples.length; x += 2)
{
samples[x] = filterInphase(mTemporaryBuffer[x]);
samples[x + 1] = filterQuadrature(mTemporaryBuffer[x + 1]);
}
return samples;
}
/**
* Filters the complex samples from the buffer and returns a new complex buffer with the filtered output
*
* @param originalBuffer with complex samples to filter
* @return new buffer containing filtered complex samples
*/
public ReusableComplexBuffer filter(ReusableComplexBuffer originalBuffer)
{
ReusableComplexBuffer filteredBuffer = mReusableComplexBufferQueue.getBuffer(originalBuffer.getSamples().length);
filteredBuffer.setTimestamp(originalBuffer.getTimestamp());
float[] samples = originalBuffer.getSamples();
float[] filteredSamples = filteredBuffer.getSamples();
for(int x = 0; x < originalBuffer.getSamples().length; x += 2)
{
filteredSamples[x] = filterInphase(samples[x]);
filteredSamples[x + 1] = filterQuadrature(samples[x + 1]);
}
originalBuffer.decrementUserCount();
filteredBuffer.incrementUserCount();
return filteredBuffer;
}
@Override
public void dispose()
{
mIFilter.dispose();
mQFilter.dispose();
mReusableComplexBufferQueue.dispose();
}
}

View File

@@ -1,68 +0,0 @@
package io.github.dsheirer.dsp.filter.fir.complex;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexBuffer;
@Deprecated //Use ComplexFIRFilter2 instead
public class ComplexFIRFilter_CB_CB extends ComplexFIRFilter implements Listener<ComplexBuffer>
{
private Listener<ComplexBuffer> mListener;
/**
* Complex FIR Filter for processing complex buffers. Wraps two real FIR
* filters for processing each of the inphase and quadrature samples.
*
* @param coefficients - odd length symmetric filter taps
* @param gain - gain value to apply to each of the filtered samples
*/
public ComplexFIRFilter_CB_CB(float[] coefficients, float gain)
{
super(coefficients, gain);
}
public void dispose()
{
mListener = null;
}
@Override
public void receive(ComplexBuffer buffer)
{
if(mListener != null)
{
float[] samples = buffer.getSamples();
for(int x = 0; x < samples.length; x += 2)
{
samples[x] = filterInphase(samples[x]);
samples[x + 1] = filterQuadrature(samples[x + 1]);
}
mListener.receive(buffer);
}
}
/**
* Filters the samples and send the results to the registered listener
*/
public void receive(float[] samples)
{
if(mListener != null)
{
float[] filtered = filter(samples);
//TODO: this should be a reusable buffer here.
mListener.receive(new ComplexBuffer(filtered));
}
}
public void setListener(Listener<ComplexBuffer> listener)
{
mListener = listener;
}
public void removeListener()
{
mListener = null;
}
}

View File

@@ -1,136 +0,0 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.filter.fir.real;
import io.github.dsheirer.dsp.filter.fir.FIRFilter;
@Deprecated //Use RealFIRFilter2 instead
public class RealFIRFilter extends FIRFilter
{
private float[] mBuffer;
private int mBufferSize = 1;
private int mBufferPointer = 0;
private int[][] mIndexMap;
private float[] mCoefficients;
private float mGain;
private float mAccumulator;
/**
* Float sample FIR filter base class.
*
* @param coefficients - filter coefficients
* @param gain value to apply to the filtered output - use 1.0f for no gain
*/
public RealFIRFilter(float[] coefficients, float gain)
{
mCoefficients = coefficients;
mGain = gain;
mBufferSize = mCoefficients.length;
mBuffer = new float[mBufferSize];
mBufferPointer = mBufferSize - 1;
generateIndexMap(mBufferSize);
}
public float[] getCoefficients()
{
return mCoefficients;
}
@Override
public void dispose()
{
mCoefficients = null;
mIndexMap = null;
mBuffer = null;
}
/**
* Loads the sample into the circular buffer without calculating a filtered value.
* @param sample to load
*/
public void load(float sample)
{
mBuffer[mBufferPointer] = sample;
mBufferPointer--;
if(mBufferPointer < 0)
{
mBufferPointer += mBufferSize;
}
}
/**
* Loads the sample into the circular buffer and calculates a filtered sample output.
* @param sample to load
* @return filtered value
*/
public float filter(float sample)
{
mBuffer[mBufferPointer] = sample;
mAccumulator = 0.0f;
for(int x = 0; x < mBufferSize; x++)
{
mAccumulator += mCoefficients[x] * mBuffer[mIndexMap[mBufferPointer][x]];
}
mBufferPointer--;
if(mBufferPointer < 0)
{
mBufferPointer += mBufferSize;
}
/* Apply gain and return the filtered value */
mAccumulator *= mGain;
return mAccumulator;
}
/**
* Current output value for the filter after the filter() method has been invoked.
* @return
*/
public float currentValue()
{
return mAccumulator;
}
/**
* Generates a circular buffer index map to support lookup of the translated
* index based on the current buffer pointer and the desired sample index.
*/
private void generateIndexMap(int size)
{
mIndexMap = new int[size][size];
for(int x = 0; x < size; x++)
{
for(int y = 0; y < size; y++)
{
int z = x + y;
mIndexMap[x][y] = z < size ? z : z - size;
}
}
}
}

View File

@@ -1,68 +0,0 @@
package io.github.dsheirer.dsp.filter.fir.real;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.real.RealBuffer;
@Deprecated //Use RealFIRFilter2 version instead
public class RealFIRFilter_RB_RB extends RealFIRFilter
implements Listener<RealBuffer>
{
private Listener<RealBuffer> mListener;
/**
* Real Buffer Float FIR filter with streaming float buffer provider and
* listener interfaces.
*
* @param coefficients
* @param gain
*/
public RealFIRFilter_RB_RB( float[] coefficients, float gain )
{
super( coefficients, gain );
}
@Override
public void dispose()
{
super.dispose();
mListener = null;
}
@Override
public void receive( RealBuffer buffer )
{
if( mListener != null )
{
mListener.receive( filter( buffer ) );
}
}
/**
* Filters the buffer samples
*
* @param buffer
* @return
*/
public RealBuffer filter( RealBuffer buffer )
{
float[] samples = buffer.getSamples();
for( int x = 0; x < samples.length; x++ )
{
samples[ x ] = filter( samples[ x ] );
}
return buffer;
}
public void setListener( Listener<RealBuffer> listener )
{
mListener = listener;
}
public void removeListener()
{
mListener = null;
}
}

View File

@@ -0,0 +1,106 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.filter.halfband.complex;
import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
public class ComplexHalfBandFilter
{
private HalfBandFilter mIFilter;
private HalfBandFilter mQFilter;
private boolean mDecimate;
private boolean mDecimateFlag;
/**
* @param coefficients
* @param gain
* @param decimate
*/
public ComplexHalfBandFilter(float[] coefficients, float gain, boolean decimate)
{
mIFilter = new HalfBandFilter(coefficients, gain);
mQFilter = new HalfBandFilter(coefficients, gain);
mDecimate = decimate;
}
public void dispose()
{
mIFilter.dispose();
mQFilter.dispose();
}
public ReusableComplexBuffer filter(ReusableComplexBuffer buffer)
{
throw new IllegalStateException("This filter has to be updated to work with reusable buffers");
// if(mDecimate)
// {
// float[] samples = buffer.getSamples();
//
// int half = samples.length / 2;
//
// float[] decimated;
//
// if(half % 2 == 0 || mDecimateFlag)
// {
// decimated = new float[half];
// }
// else
// {
// /* If inbound buffer size is odd-length, then we have to
// * adjust when the first operation is non-decimation, since
// * that will produce an outbound buffer 1 sample larger */
// decimated = new float[half + 1];
// }
//
// int decimatedPointer = 0;
//
// for(int x = 0; x < samples.length; x += 2)
// {
// /* Insert the sample but don't filter */
// if(mDecimateFlag)
// {
// mIFilter.insert(samples[x]);
// mQFilter.insert(samples[x + 1]);
// }
// else
// {
// decimated[decimatedPointer++] = mIFilter.filter(samples[x]);
// decimated[decimatedPointer++] = mQFilter.filter(samples[x + 1]);
// }
//
// /* Toggle the decimation flag for every sample */
// mDecimateFlag = !mDecimateFlag;
// }
//
// //return buffer
// }
// else
// {
// /* Non Decimate - filter each of the values and return the buffer */
// float[] samples = buffer.getSamples();
//
// for(int x = 0; x < samples.length; x += 2)
// {
// samples[x] = mIFilter.filter(samples[x]);
// samples[x + 1] = mQFilter.filter(samples[x + 1]);
// }
//
// //return buffer
// }
}
}

View File

@@ -1,117 +0,0 @@
package io.github.dsheirer.dsp.filter.halfband.complex;
import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexBuffer;
public class HalfBandFilter_CB_CB implements Listener<ComplexBuffer>
{
private HalfBandFilter mIFilter;
private HalfBandFilter mQFilter;
private boolean mDecimate;
private boolean mDecimateFlag;
private Listener<ComplexBuffer> mListener;
/**
*
* @param coefficients
* @param gain
* @param decimate
*/
public HalfBandFilter_CB_CB( float[] coefficients, float gain, boolean decimate )
{
mIFilter = new HalfBandFilter( coefficients, gain );
mQFilter = new HalfBandFilter( coefficients, gain );
mDecimate = decimate;
}
public void dispose()
{
mIFilter.dispose();
mQFilter.dispose();
mListener = null;
}
@Override
public void receive( ComplexBuffer buffer )
{
if( mListener != null )
{
if( mDecimate )
{
float[] samples = buffer.getSamples();
int half = samples.length / 2;
float[] decimated;
if( half % 2 == 0 || mDecimateFlag )
{
decimated = new float[ half ];
}
else
{
/* If inbound buffer size is odd-length, then we have to
* adjust when the first operation is non-decimation, since
* that will produce an outbound buffer 1 sample larger */
decimated = new float[ half + 1 ];
}
int decimatedPointer = 0;
for( int x = 0; x < samples.length; x += 2 )
{
/* Insert the sample but don't filter */
if( mDecimateFlag )
{
mIFilter.insert( samples[ x ] );
mQFilter.insert( samples[ x + 1 ] );
}
else
{
decimated[ decimatedPointer++ ] = mIFilter.filter( samples[ x ] );
decimated[ decimatedPointer++ ] = mQFilter.filter( samples[ x + 1 ] );
}
/* Toggle the decimation flag for every sample */
mDecimateFlag = !mDecimateFlag;
}
if( mListener != null )
{
mListener.receive( new ComplexBuffer( decimated ) );
}
}
else
{
/* Non Decimate - filter each of the values and return the buffer */
float[] samples = buffer.getSamples();
for( int x = 0; x < samples.length; x += 2 )
{
samples[ x ] = mIFilter.filter( samples[ x ] );
samples[ x + 1 ] = mQFilter.filter( samples[ x + 1 ] );
}
if( mListener != null )
{
mListener.receive( buffer );
}
}
}
}
public void setListener( Listener<ComplexBuffer> listener )
{
mListener = listener;
}
public void removeListener()
{
mListener = null;
}
}

View File

@@ -1,162 +1,189 @@
/*******************************************************************************
* 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/>
******************************************************************************/
package io.github.dsheirer.dsp.filter.halfband.real;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import org.apache.commons.lang3.Validate;
public class HalfBandFilter
{
private float[] mBuffer;
private float[] mBuffer;
private int mBufferSize = 1; //Temporary initial value
private int mBufferPointer = 0;
private float mGain;
private float[] mCoefficients;
private int[][] mIndexMap;
private int mCenterCoefficient;
private int mCenterCoefficientMapIndex;
private int mBufferPointer = 0;
/**
* Half-Band filter for processing real float samples.
*
* Takes advantage of the 0-valued FIR half-band coefficents inherent
* in the half-band filter and does not calculate those coefficients.
*
* This reduces the workload to (tap-size - 1) / 4 + 1 calculations per sample.
*
* @param filter - filter coefficients
* @param gain - gain multiplier. Use 1.0 for unity/no gain
*/
public HalfBandFilter( float[] coefficients, float gain )
{
mCoefficients = coefficients;
mGain = gain;
mBufferSize = mCoefficients.length;
mBuffer = new float[ mBufferSize ];
generateIndexMap( mCoefficients.length );
}
private float mGain;
private float[] mCoefficients;
private int[][] mIndexMap;
public void dispose()
{
}
private int mCenterCoefficient;
private int mCenterCoefficientMapIndex;
/**
* Inserts the sample into the buffer without calculating a filtered value
*/
public void insert( float sample )
{
mBuffer[ mBufferPointer ] = sample;
private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("HalfBandFilter");
mBufferPointer++;
mBufferPointer = mBufferPointer % mBufferSize;
}
/**
* Half-Band filter for processing real float samples.
*
* Takes advantage of the 0-valued FIR half-band coefficents inherent
* in the half-band filter and does not calculate those coefficients.
*
* This reduces the workload to (tap-size - 1) / 4 + 1 calculations per sample.
*
* @param filter - filter coefficients
* @param gain - gain multiplier. Use 1.0 for unity/no gain
*/
public HalfBandFilter(float[] coefficients, float gain)
{
//TODO: update this to use SIMD structures with 2 float arrays and a toggle operation
/**
* Calculate the filtered value by applying the coefficients against
* the complex samples in mBuffer
*/
public float filter( float sample )
{
insert( sample );
float accumulator = 0;
//Start with the center tap value
accumulator += mCoefficients[ mCenterCoefficient ] *
mBuffer[ mIndexMap[ mBufferPointer ][ mCenterCoefficientMapIndex ] ];
//For the remaining coefficients, add the symmetric samples, oldest and newest
//first, then multiply by the single coefficient
for( int x = 0; x < mCenterCoefficientMapIndex; x += 2 )
{
accumulator += mCoefficients[ x ] *
( mBuffer[ mIndexMap[ mBufferPointer ][ x ] ] +
mBuffer[ mIndexMap[ mBufferPointer ][ x + 1 ] ] );
}
mCoefficients = coefficients;
mGain = gain;
return accumulator * mGain;
}
/**
* Creates an n X (n + 1 / 2) index map enabling quick access to the
* circular buffer samples.
*
* As the buffer shifts right with each subsequent sample, we have to move
* the index pointers with it, for efficient access of the samples.
*
* The first array index value in the index map corresponds to the current
* buffer pointer location.
*
* The second array index value points to the samples that should be
* multiplied by the coefficients as follows:
*
* 0 = center tap sample, to be multiplied by center coefficient
*
* 0 = sample( 1 )
* 1 = sample( size - 1 )
*
* Indexes 0 and 1 will be multiplied by coefficient( 0 ).
*
* Subsequent indexes 3, 4, etc, point to the oldest and newest samples that
* correspond to the matching ( 3 ) coefficient index.
*
* @param odd-sized number of filter taps (ie coefficients) and buffer
*/
private void generateIndexMap( int size )
{
//Ensure we have an odd size
Validate.isTrue(size % 2 == 1);
mBufferSize = mCoefficients.length;
mBuffer = new float[mBufferSize];
int mapWidth = ( ( size + 1 ) / 2 ) + 1;
//Set center tap index for coefficients array
mCenterCoefficient = ( size - 1 ) / 2;
mCenterCoefficientMapIndex = mCenterCoefficient + 1;
generateIndexMap(mCoefficients.length);
}
mIndexMap = new int[ size ][ mapWidth ];
public HalfBandFilter(float[] coefficients, float gain, boolean decimate)
{
this(coefficients, gain);
//Setup the first row, buffer pointer index 0, as a starting point
for( int x = 0; x < mapWidth - 2; x += 2 )
{
mIndexMap[ 0 ][ x ] = x;
mIndexMap[ 0 ][ x + 1 ] = size - 1 - x;
}
throw new IllegalStateException("This filter must be updated to support non-decimate option");
}
//Place center tap index in last element
mIndexMap[ 0 ][ mCenterCoefficientMapIndex ] = mCenterCoefficient;
//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 dispose()
{
}
/**
* Inserts the sample into the buffer without calculating a filtered value
*/
public void insert(float sample)
{
mBuffer[mBufferPointer] = sample;
mBufferPointer++;
mBufferPointer = mBufferPointer % mBufferSize;
}
public ReusableBuffer filter(ReusableBuffer input)
{
float[] inputSamples = input.getSamples();
ReusableBuffer output = mReusableBufferQueue.getBuffer(inputSamples.length);
float[] outputSamples = output.getSamples();
for(int x = 0; x < inputSamples.length; x++)
{
outputSamples[x] = filter(inputSamples[x]);
}
output.incrementUserCount();
input.decrementUserCount();
return output;
}
/**
* Calculate the filtered value by applying the coefficients against the samples in mBuffer
*/
public float filter(float sample)
{
insert(sample);
float accumulator = 0;
//Start with the center tap value
accumulator += mCoefficients[mCenterCoefficient] * mBuffer[mIndexMap[mBufferPointer][mCenterCoefficientMapIndex]];
//For the remaining coefficients, add the symmetric samples, oldest and newest
//first, then multiply by the single coefficient
for(int x = 0; x < mCenterCoefficientMapIndex; x += 2)
{
accumulator += mCoefficients[x] * (mBuffer[mIndexMap[mBufferPointer][x]] + mBuffer[mIndexMap[mBufferPointer][x + 1]]);
}
return accumulator * mGain;
}
/**
* Creates an n X (n + 1 / 2) index map enabling quick access to the
* circular buffer samples.
*
* As the buffer shifts right with each subsequent sample, we have to move
* the index pointers with it, for efficient access of the samples.
*
* The first array index value in the index map corresponds to the current
* buffer pointer location.
*
* The second array index value points to the samples that should be
* multiplied by the coefficients as follows:
*
* 0 = center tap sample, to be multiplied by center coefficient
*
* 0 = sample( 1 )
* 1 = sample( size - 1 )
*
* Indexes 0 and 1 will be multiplied by coefficient( 0 ).
*
* Subsequent indexes 3, 4, etc, point to the oldest and newest samples that
* correspond to the matching ( 3 ) coefficient index.
*
* @param odd-sized number of filter taps (ie coefficients) and buffer
*/
private void generateIndexMap(int size)
{
//Ensure we have an odd size
Validate.isTrue(size % 2 == 1);
int mapWidth = ((size + 1) / 2) + 1;
//Set center tap index for coefficients array
mCenterCoefficient = (size - 1) / 2;
mCenterCoefficientMapIndex = mCenterCoefficient + 1;
mIndexMap = new int[size][mapWidth];
//Setup the first row, buffer pointer index 0, as a starting point
for(int x = 0; x < mapWidth - 2; x += 2)
{
mIndexMap[0][x] = x;
mIndexMap[0][x + 1] = size - 1 - x;
}
//Place center tap index in last element
mIndexMap[0][mCenterCoefficientMapIndex] = mCenterCoefficient;
//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;
}
}
}
}
}

View File

@@ -1,117 +0,0 @@
package io.github.dsheirer.dsp.filter.halfband.real;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.real.RealBuffer;
public class HalfBandFilter_RB_RB extends HalfBandFilter implements Listener<RealBuffer>
{
private boolean mDecimate;
private boolean mDecimateFlag;
private Listener<RealBuffer> mListener;
/**
* Halfband filter for real buffers with optional decimation by two.
*
* 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 )
{
super( coefficients, gain );
mDecimate = decimate;
}
public void dispose()
{
super.dispose();
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();
int half = samples.length / 2;
float[] decimated;
if( half % 2 == 0 || mDecimateFlag )
{
decimated = new float[ half ];
}
else
{
/* If inbound buffer size is odd-length, then we have to
* adjust when the first operation is non-decimation, since
* that will produce an outbound buffer 1 sample larger */
decimated = new float[ half + 1 ];
}
int decimatedPointer = 0;
for( float sample: samples )
{
/* Insert the sample but don't filter */
if( mDecimateFlag )
{
insert( sample );
}
else
{
decimated[ decimatedPointer++ ] = filter( sample );
}
/* Toggle the decimation flag for every sample */
mDecimateFlag = !mDecimateFlag;
}
return new RealBuffer( decimated );
}
else
{
/* Non Decimate - filter each of the values and return the buffer */
float[] samples = buffer.getSamples();
for( int x = 0; x < samples.length; x++ )
{
samples[ x ] = filter( samples[ x ] );
}
return buffer;
}
}
public void setListener( Listener<RealBuffer> listener )
{
mListener = listener;
}
public void removeListener()
{
mListener = null;
}
}

View File

@@ -1,281 +1,245 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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 io.github.dsheirer.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 io.github.dsheirer.dsp.filter.Filters;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.sample.real.RealBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
public class HilbertTransform implements Listener<RealBuffer>
public class HilbertTransform
{
private static final Logger mLog = LoggerFactory.getLogger( HilbertTransform.class );
private static final Logger mLog = LoggerFactory.getLogger(HilbertTransform.class);
private float[] mHilbertFilter;
private Listener<ComplexBuffer> mListener;
private float[] mBuffer;
private float[] mHilbertFilter;
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 ];
private int mBufferPointer;
private int[][] mIndexMap;
private int mMapHeight;
private int mCenterTapIndex;
private float mCenterCoefficent;
generateIndexMap( mHilbertFilter.length );
}
private boolean mInvertFlag = false;
@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() ) );
}
/**
* 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());
/**
* 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;
mBufferSize = mHilbertFilter.length + 1;
mBuffer = new float[mBufferSize];
for( int y = 0; y < samples.length; y +=2 )
{
insert( samples[ y ] );
insert( samples[ y + 1 ] );
accumulator = 0.0f;
mCenterCoefficent = mHilbertFilter[mHilbertFilter.length / 2];
int index = mBufferPointer / 2;
generateIndexMap(mHilbertFilter.length);
}
for( int x = 0; x < mHilbertFilter.length / 2; x += 2 )
{
accumulator += mHilbertFilter[ x ] *
( mBuffer[ mIndexMap[ index ][ x + 1] ] -
mBuffer[ mIndexMap[ index ][ x ] ] );
}
/**
* 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;
//Perform FS/2 frequency translation on the final filtered values
if( mInvertFlag )
{
//inphase
samples[ y ] = -( mBuffer[ mIndexMap[ index ][ mCenterTapIndex ] ] );
for(int y = 0; y < samples.length; y += 2)
{
insert(samples[y]);
insert(samples[y + 1]);
//quadrature
samples[ y + 1 ] = -accumulator;
}
else
{
//inphase
samples[ y ] = mBuffer[ mIndexMap[ index ][ mCenterTapIndex ] ];
accumulator = 0.0f;
//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;
int index = mBufferPointer / 2;
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 ];
for(int x = 0; x < mHilbertFilter.length / 2; x += 2)
{
accumulator += mHilbertFilter[x] *
(mBuffer[mIndexMap[index][x + 1]] -
mBuffer[mIndexMap[index][x]]);
}
//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;
//Perform FS/2 frequency translation on the final filtered values
if(mInvertFlag)
{
//inphase
samples[y] = -(mBuffer[mIndexMap[index][mCenterTapIndex]]);
//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;
//quadrature
samples[y + 1] = -accumulator;
}
else
{
//inphase
samples[y] = mBuffer[mIndexMap[index][mCenterTapIndex]];
//Special handling for center tap wrap around
if( y == mCenterTapIndex && mIndexMap[ x ][ y ] < 0 )
{
mIndexMap[ x ][ y ] = size;
}
}
}
}
}
//quadrature
samples[y + 1] = accumulator;
}
/**
* Logs the index map contents
*/
public void logIndexMap()
{
for( int[] indexSet: mIndexMap )
{
mLog.debug( "Row:" + Arrays.toString( indexSet ) );
}
}
mInvertFlag = !mInvertFlag;
}
/**
* 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.
*
* 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 )
{
mHilbertFilter = new float[ coefficients.length ];
int middle = coefficients.length / 2;
for( int x = 0; x < coefficients.length; x++ )
{
if( x < middle )
{
mHilbertFilter[ x ] = 2.0f * -Math.abs( coefficients[ x ] );
}
else if( x > middle )
{
mHilbertFilter[ x ] = 2.0f * Math.abs( coefficients[ x ] );
}
else
{
mHilbertFilter[ x ] = 2.0f * coefficients[ x ];
}
}
}
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.
*
* 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)
{
mHilbertFilter = new float[coefficients.length];
int middle = coefficients.length / 2;
for(int x = 0; x < coefficients.length; x++)
{
if(x < middle)
{
mHilbertFilter[x] = 2.0f * -Math.abs(coefficients[x]);
}
else if(x > middle)
{
mHilbertFilter[x] = 2.0f * Math.abs(coefficients[x]);
}
else
{
mHilbertFilter[x] = 2.0f * coefficients[x];
}
}
}
}

View File

@@ -1,280 +0,0 @@
package io.github.dsheirer.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 io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.sample.real.RealBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
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 ];
}
}
}
}

View File

@@ -1,7 +1,20 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.filter.iir;
import io.github.dsheirer.sample.real.RealBuffer;
public class DeemphasisFilter
{
private static final float MAX_SAMPLE_VALUE = 0.95f;
@@ -45,16 +58,4 @@ public class DeemphasisFilter
return samples;
}
public RealBuffer filter( RealBuffer buffer )
{
float[] samples = buffer.getSamples();
for( int x = 0; x < samples.length; x++ )
{
samples[ x ] = filter( samples[ x ] );
}
return buffer;
}
}

View File

@@ -19,7 +19,6 @@ import io.github.dsheirer.buffer.FloatCircularBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
import io.github.dsheirer.sample.complex.Complex;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.sample.complex.ComplexSampleListener;
public class ComplexFeedForwardGainControl implements ComplexSampleListener
@@ -137,17 +136,6 @@ public class ComplexFeedForwardGainControl implements ComplexSampleListener
return processed;
}
/**
* Applies gain to the complex sample buffer
* @param buffer to apply gain
* @return buffer with gain applied samples
*/
public ComplexBuffer filter(ComplexBuffer buffer)
{
float[] filtered = filter(buffer.getSamples());
return new ComplexBuffer(filtered);
}
/**
* Applies gain to the complex sample buffer
* @param buffer to apply gain

View File

@@ -1,7 +1,5 @@
package io.github.dsheirer.dsp.gain;
import io.github.dsheirer.sample.real.RealBuffer;
public class NonClippingGain
{
private float mGain;
@@ -50,16 +48,4 @@ public class NonClippingGain
return samples;
}
/**
* Applies gain to the real buffer
*/
public RealBuffer apply( RealBuffer buffer )
{
float[] samples = buffer.getSamples();
apply( samples );
return buffer;
}
}

View File

@@ -21,11 +21,11 @@ import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.mixer.FS4DownConverter;
import io.github.dsheirer.dsp.mixer.IOscillator;
import io.github.dsheirer.dsp.mixer.LowPhaseNoiseOscillator;
import io.github.dsheirer.sample.IOverflowListener;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.SampleType;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
import io.github.dsheirer.sample.real.IOverflowListener;
import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;

View File

@@ -54,11 +54,6 @@ import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.real.IFilteredRealBufferListener;
import io.github.dsheirer.sample.real.IFilteredRealBufferProvider;
import io.github.dsheirer.sample.real.IUnFilteredRealBufferListener;
import io.github.dsheirer.sample.real.IUnFilteredRealBufferProvider;
import io.github.dsheirer.sample.real.RealBuffer;
import io.github.dsheirer.source.ComplexSource;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.ISourceEventProvider;
@@ -97,18 +92,16 @@ public class ProcessingChain implements IChannelEventListener
{
private final static Logger mLog = LoggerFactory.getLogger(ProcessingChain.class);
private Broadcaster<AttributeChangeRequest> mAttributeChangeRequestBroadcaster = new Broadcaster<>();
private ReusableBufferBroadcaster<ReusableAudioPacket> mAudioPacketBroadcaster = new ReusableBufferBroadcaster<>();
private ReusableBufferBroadcaster<ReusableBuffer> mDemodulatedAudioBufferBroadcaster = new ReusableBufferBroadcaster();
private ReusableBufferBroadcaster<ReusableComplexBuffer> mBasebandComplexBufferBroadcaster = new ReusableBufferBroadcaster();
private Broadcaster<AttributeChangeRequest> mAttributeChangeRequestBroadcaster = new Broadcaster<>();
private Broadcaster<CallEvent> mCallEventBroadcaster = new Broadcaster<>();
private Broadcaster<ChannelEvent> mChannelEventBroadcaster = new Broadcaster<>();
private ReusableBufferBroadcaster<ReusableComplexBuffer> mComplexBufferBroadcaster = new ReusableBufferBroadcaster();
private ReusableBufferBroadcaster<ReusableBuffer> mDemodulatedAudioBufferBroadcaster = new ReusableBufferBroadcaster();
private Broadcaster<DecoderStateEvent> mDecoderStateEventBroadcaster = new Broadcaster<>();
private Broadcaster<Heartbeat> mHeartbeatBroadcaster = new Broadcaster<>();
private Broadcaster<SourceEvent> mSourceEventBroadcaster = new Broadcaster<>();
private Broadcaster<Message> mMessageBroadcaster = new Broadcaster<>();
private Broadcaster<RealBuffer> mFilteredRealBufferBroadcaster = new Broadcaster<>();
private Broadcaster<RealBuffer> mUnFilteredRealBufferBroadcaster = new Broadcaster<>();
private Broadcaster<SquelchState> mSquelchStateBroadcaster = new Broadcaster<>();
private AtomicBoolean mRunning = new AtomicBoolean();
@@ -169,9 +162,8 @@ public class ProcessingChain implements IChannelEventListener
mAudioPacketBroadcaster.dispose();
mCallEventBroadcaster.dispose();
mChannelEventBroadcaster.dispose();
mComplexBufferBroadcaster.dispose();
mBasebandComplexBufferBroadcaster.dispose();
mMessageBroadcaster.dispose();
mFilteredRealBufferBroadcaster.dispose();
mSquelchStateBroadcaster.dispose();
}
@@ -316,11 +308,6 @@ public class ProcessingChain implements IChannelEventListener
mDecoderStateEventBroadcaster.addListener(((IDecoderStateEventListener) module).getDecoderStateListener());
}
if(module instanceof IFilteredRealBufferListener)
{
mFilteredRealBufferBroadcaster.addListener(((IFilteredRealBufferListener) module).getFilteredRealBufferListener());
}
if(module instanceof IHeartbeatListener)
{
mHeartbeatBroadcaster.addListener(((IHeartbeatListener)module).getHeartbeatListener());
@@ -338,7 +325,7 @@ public class ProcessingChain implements IChannelEventListener
if(module instanceof IReusableComplexBufferListener)
{
mComplexBufferBroadcaster.addListener(((IReusableComplexBufferListener)module).getReusableComplexBufferListener());
mBasebandComplexBufferBroadcaster.addListener(((IReusableComplexBufferListener)module).getReusableComplexBufferListener());
}
if(module instanceof ISourceEventListener)
@@ -355,11 +342,6 @@ public class ProcessingChain implements IChannelEventListener
{
mSquelchStateBroadcaster.addListener(((ISquelchStateListener) module).getSquelchStateListener());
}
if(module instanceof IUnFilteredRealBufferListener)
{
mUnFilteredRealBufferBroadcaster.addListener(((IUnFilteredRealBufferListener) module).getUnFilteredRealBufferListener());
}
}
/**
@@ -393,11 +375,6 @@ public class ProcessingChain implements IChannelEventListener
mDecoderStateEventBroadcaster.removeListener(((IDecoderStateEventListener) module).getDecoderStateListener());
}
if(module instanceof IFilteredRealBufferListener)
{
mFilteredRealBufferBroadcaster.removeListener(((IFilteredRealBufferListener) module).getFilteredRealBufferListener());
}
if(module instanceof IHeartbeatListener)
{
mHeartbeatBroadcaster.removeListener(((IHeartbeatListener)module).getHeartbeatListener());
@@ -410,7 +387,7 @@ public class ProcessingChain implements IChannelEventListener
if(module instanceof IReusableComplexBufferListener)
{
mComplexBufferBroadcaster.removeListener(((IReusableComplexBufferListener) module).getReusableComplexBufferListener());
mBasebandComplexBufferBroadcaster.removeListener(((IReusableComplexBufferListener) module).getReusableComplexBufferListener());
}
if(module instanceof ISourceEventListener)
@@ -427,11 +404,6 @@ public class ProcessingChain implements IChannelEventListener
{
mSquelchStateBroadcaster.removeListener(((ISquelchStateListener) module).getSquelchStateListener());
}
if(module instanceof IUnFilteredRealBufferListener)
{
mUnFilteredRealBufferBroadcaster.removeListener(((IUnFilteredRealBufferListener) module).getUnFilteredRealBufferListener());
}
}
/**
@@ -475,11 +447,6 @@ public class ProcessingChain implements IChannelEventListener
((IMessageProvider) module).setMessageListener(mMessageBroadcaster);
}
if(module instanceof IFilteredRealBufferProvider)
{
((IFilteredRealBufferProvider) module).setFilteredRealBufferListener(mFilteredRealBufferBroadcaster);
}
if(module instanceof IMessageProvider)
{
((IMessageProvider) module).setMessageListener(mMessageBroadcaster);
@@ -499,11 +466,6 @@ public class ProcessingChain implements IChannelEventListener
{
((ISquelchStateProvider) module).setSquelchStateListener(mSquelchStateBroadcaster);
}
if(module instanceof IUnFilteredRealBufferProvider)
{
((IUnFilteredRealBufferProvider) module).setUnFilteredRealBufferListener(mUnFilteredRealBufferBroadcaster);
}
}
/**
@@ -547,11 +509,6 @@ public class ProcessingChain implements IChannelEventListener
((IMessageProvider) module).setMessageListener(null);
}
if(module instanceof IFilteredRealBufferProvider)
{
((IFilteredRealBufferProvider) module).setFilteredRealBufferListener(null);
}
if(module instanceof IMessageProvider)
{
((IMessageProvider) module).setMessageListener(null);
@@ -571,11 +528,6 @@ public class ProcessingChain implements IChannelEventListener
{
((ISquelchStateProvider) module).setSquelchStateListener(null);
}
if(module instanceof IUnFilteredRealBufferProvider)
{
((IUnFilteredRealBufferProvider) module).setUnFilteredRealBufferListener(null);
}
}
/**
@@ -609,7 +561,7 @@ public class ProcessingChain implements IChannelEventListener
switch(mSource.getSampleType())
{
case COMPLEX:
((ComplexSource) mSource).setListener(mComplexBufferBroadcaster);
((ComplexSource) mSource).setListener(mBasebandComplexBufferBroadcaster);
break;
case REAL:
((RealSource) mSource).setListener(mDemodulatedAudioBufferBroadcaster);
@@ -657,7 +609,7 @@ public class ProcessingChain implements IChannelEventListener
switch(mSource.getSampleType())
{
case COMPLEX:
((ComplexSource) mSource).removeListener(mComplexBufferBroadcaster);
((ComplexSource) mSource).removeListener(mBasebandComplexBufferBroadcaster);
break;
case REAL:
((RealSource) mSource).removeListener(mDemodulatedAudioBufferBroadcaster);
@@ -796,6 +748,9 @@ public class ProcessingChain implements IChannelEventListener
}
}
/**
* Removes the listener from receiving decoded messages from all decoders.
*/
public void removeMessageListener(Listener<Message> listener)
{
mMessageBroadcaster.removeListener(listener);
@@ -832,14 +787,20 @@ public class ProcessingChain implements IChannelEventListener
mSquelchStateBroadcaster.removeListener(listener);
}
public void addRealBufferListener(Listener<RealBuffer> listener)
/**
* Adds listener to receive demodulated audio buffers from an modules that produce demodulated audio.
*/
public void addDemodulatedAudioListener(Listener<ReusableBuffer> listener)
{
mFilteredRealBufferBroadcaster.addListener(listener);
mDemodulatedAudioBufferBroadcaster.addListener(listener);
}
public void removeRealBufferListener(Listener<RealBuffer> listener)
/**
* Removes the listener from receiving demodulated audio buffers.
*/
public void removeDemodulatedAudioListener(Listener<ReusableBuffer> listener)
{
mFilteredRealBufferBroadcaster.removeListener(listener);
mDemodulatedAudioBufferBroadcaster.removeListener(listener);
}
@Override

View File

@@ -2,13 +2,13 @@ package io.github.dsheirer.module.decode.p25;
import io.github.dsheirer.dsp.gain.DirectGainControl;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.real.RealBuffer;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.real.RealSampleListener;
import io.github.dsheirer.sample.real.RealSampleProvider;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.SourceEvent;
public class C4FMSymbolFilter implements Listener<RealBuffer>, ISourceEventListener, RealSampleProvider
public class C4FMSymbolFilter implements Listener<ReusableBuffer>, ISourceEventListener, RealSampleProvider
{
private static final float TAPS[][] =
{
@@ -215,7 +215,7 @@ public class C4FMSymbolFilter implements Listener<RealBuffer>, ISourceEventListe
}
@Override
public void receive(RealBuffer buffer)
public void receive(ReusableBuffer buffer)
{
for(float sample : buffer.getSamples())
{

View File

@@ -1,3 +1,18 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.audio;
import io.github.dsheirer.audio.AudioFormats;

View File

@@ -20,10 +20,10 @@ import io.github.dsheirer.channel.metadata.Metadata;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.record.wave.AudioPacketWaveRecorder;
import io.github.dsheirer.record.wave.ComplexBufferWaveRecorder;
import io.github.dsheirer.sample.IOverflowListener;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.OverflowableTransferQueue;
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
import io.github.dsheirer.sample.real.IOverflowListener;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -1,7 +1,6 @@
package io.github.dsheirer.sample;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.real.RealBuffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -53,24 +52,6 @@ public class ConversionUtils
return converted;
}
/**
* Converts the float samples in a complex buffer to a little endian 16-bit
* buffer
*/
public static ByteBuffer convertToSigned16BitSamples(Buffer buffer)
{
return convertToSigned16BitSamples(buffer.getSamples());
}
/**
* Converts the float samples in a complex buffer to a little endian 16-bit
* buffer
*/
public static ByteBuffer convertToSigned16BitSamples(RealBuffer buffer)
{
return convertToSigned16BitSamples(buffer.getSamples());
}
/**
* Converts the float samples in a complex buffer to a little endian 16-bit
* buffer

View File

@@ -13,17 +13,13 @@
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.sample.complex;
package io.github.dsheirer.sample;
import io.github.dsheirer.sample.Buffer;
public class ComplexBuffer extends Buffer
public interface IOverflowListener
{
/**
* Wrapper around float array containing interleaved I/Q samples
* Indicates that the source is in overflow state (true) or out of overflow state (false)
* @param overflow true to indicate an overflow state
*/
public ComplexBuffer(float[] samples)
{
super(samples);
}
void sourceOverflow(boolean overflow);
}

View File

@@ -18,7 +18,6 @@
******************************************************************************/
package io.github.dsheirer.sample;
import io.github.dsheirer.sample.real.IOverflowListener;
import io.github.dsheirer.source.Source;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -15,131 +15,66 @@
******************************************************************************/
package io.github.dsheirer.sample.buffer;
import io.github.dsheirer.dsp.filter.channelizer.SampleTimestampManager;
import io.github.dsheirer.sample.Listener;
import org.apache.commons.lang3.Validate;
import java.nio.FloatBuffer;
import io.github.dsheirer.sample.real.RealSampleListener;
/**
* Assembles complex float samples into a ComplexBuffer containing an array of floats. Monitors incoming sample count
* to accurately assign a relative timestamp to each assembled buffer.
* Assembles single float samples into a ReusableBuffer containing an array of floats
*/
public class ReusableBufferAssembler
public class ReusableBufferAssembler implements RealSampleListener
{
private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("ReusableBufferAssembler");
private SampleTimestampManager mTimestampManager;
private FloatBuffer mBuffer;
private int mBufferSize;
private long mCurrentBufferTimestamp;
private Listener<ReusableComplexBuffer> mListener;
private int mBufferPointer;
private ReusableBuffer mCurrentBuffer;
private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("Real");
public ReusableBufferAssembler(int bufferSize, double sampleRate)
private Listener<ReusableBuffer> mListener;
public ReusableBufferAssembler(int bufferSize)
{
Validate.isTrue(bufferSize % 2 == 0);
mBufferSize = bufferSize;
mBuffer = FloatBuffer.allocate(mBufferSize);
mTimestampManager = new SampleTimestampManager(sampleRate);
}
/**
* Updates the reference timestamp to use for all subsequent samples that are assembled. This timestamp should
* identify the relative timestamp of the first/next sample that is received by this assembler.
* @param timestamp to use relative to the incoming sample arrays.
*/
public void updateTimestamp(long timestamp)
{
mTimestampManager.setReferenceTimestamp(timestamp);
if(mBuffer.position() == 0)
{
mCurrentBufferTimestamp = timestamp;
}
}
public void dispose()
{
mReusableComplexBufferQueue.dispose();
mListener = null;
}
/**
* Adds the samples to this assembler. As each buffer is assembled, it will be dispatched to the registered
* listener.
*
* @param samples to assemble
*/
public void receive(float[] samples)
public void reset()
{
if(mBuffer.remaining() >= samples.length)
{
mBuffer.put(samples);
mTimestampManager.increment(samples.length / 2);
mCurrentBuffer.decrementUserCount();
mCurrentBuffer = null;
mBufferPointer = 0;
}
if(!mBuffer.hasRemaining())
@Override
public void receive(float sample)
{
if(mCurrentBuffer == null)
{
mCurrentBuffer = mReusableBufferQueue.getBuffer(mBufferSize);
mCurrentBuffer.incrementUserCount();
mBufferPointer = 0;
}
mCurrentBuffer.getSamples()[mBufferPointer++] = sample;
if (mBufferPointer >= mBufferSize)
{
if (mListener != null)
{
flush();
mListener.receive(mCurrentBuffer);
}
}
else
{
int offset = 0;
while(offset < samples.length)
else
{
int toCopy = mBuffer.remaining();
if((samples.length - offset) < toCopy)
{
toCopy = samples.length - offset;
}
mBuffer.put(samples, offset, toCopy);
offset += toCopy;
mTimestampManager.increment(toCopy / 2);
if(!mBuffer.hasRemaining())
{
flush();
}
mCurrentBuffer.decrementUserCount();
}
mCurrentBuffer = null;
}
}
/**
* Adds the samples to this assembler. As each buffer is assembled, it will be dispatched to the registered
* listener.
*/
public void receive(ReusableComplexBuffer samplesBuffer)
{
receive(samplesBuffer.getSamples());
samplesBuffer.decrementUserCount();
}
/**
* Flushes the current buffer contents to the registered listener
*/
public void flush()
{
if(mListener != null && mBuffer.position() > 0)
{
mBuffer.rewind();
ReusableComplexBuffer reusableComplexBuffer = mReusableComplexBufferQueue.getBuffer(mBufferSize);
reusableComplexBuffer.reloadFrom(mBuffer, mCurrentBufferTimestamp);
reusableComplexBuffer.incrementUserCount();
mListener.receive(reusableComplexBuffer);
}
mBuffer.rewind();
mCurrentBufferTimestamp = mTimestampManager.getCurrentTimestamp();
}
/**
* Sets the listener to receive the output from this buffer assembler. Note: the listener is responsible for
* managing the user count for any buffers received from this buffer assembler.
*/
public void setListener(Listener<ReusableComplexBuffer> listener)
public void setListener(Listener<ReusableBuffer> listener)
{
mListener = listener;
}

View File

@@ -0,0 +1,146 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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 io.github.dsheirer.sample.buffer;
import io.github.dsheirer.dsp.filter.channelizer.SampleTimestampManager;
import io.github.dsheirer.sample.Listener;
import org.apache.commons.lang3.Validate;
import java.nio.FloatBuffer;
/**
* Assembles complex float samples into a ComplexBuffer containing an array of floats. Monitors incoming sample count
* to accurately assign a relative timestamp to each assembled buffer.
*/
public class ReusableComplexBufferAssembler
{
private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("ReusableComplexBufferAssembler");
private SampleTimestampManager mTimestampManager;
private FloatBuffer mBuffer;
private int mBufferSize;
private long mCurrentBufferTimestamp;
private Listener<ReusableComplexBuffer> mListener;
public ReusableComplexBufferAssembler(int bufferSize, double sampleRate)
{
Validate.isTrue(bufferSize % 2 == 0);
mBufferSize = bufferSize;
mBuffer = FloatBuffer.allocate(mBufferSize);
mTimestampManager = new SampleTimestampManager(sampleRate);
}
/**
* Updates the reference timestamp to use for all subsequent samples that are assembled. This timestamp should
* identify the relative timestamp of the first/next sample that is received by this assembler.
* @param timestamp to use relative to the incoming sample arrays.
*/
public void updateTimestamp(long timestamp)
{
mTimestampManager.setReferenceTimestamp(timestamp);
if(mBuffer.position() == 0)
{
mCurrentBufferTimestamp = timestamp;
}
}
public void dispose()
{
mReusableComplexBufferQueue.dispose();
mListener = null;
}
/**
* Adds the samples to this assembler. As each buffer is assembled, it will be dispatched to the registered
* listener.
*
* @param samples to assemble
*/
public void receive(float[] samples)
{
if(mBuffer.remaining() >= samples.length)
{
mBuffer.put(samples);
mTimestampManager.increment(samples.length / 2);
if(!mBuffer.hasRemaining())
{
flush();
}
}
else
{
int offset = 0;
while(offset < samples.length)
{
int toCopy = mBuffer.remaining();
if((samples.length - offset) < toCopy)
{
toCopy = samples.length - offset;
}
mBuffer.put(samples, offset, toCopy);
offset += toCopy;
mTimestampManager.increment(toCopy / 2);
if(!mBuffer.hasRemaining())
{
flush();
}
}
}
}
/**
* Adds the samples to this assembler. As each buffer is assembled, it will be dispatched to the registered
* listener.
*/
public void receive(ReusableComplexBuffer samplesBuffer)
{
receive(samplesBuffer.getSamples());
samplesBuffer.decrementUserCount();
}
/**
* Flushes the current buffer contents to the registered listener
*/
public void flush()
{
if(mListener != null && mBuffer.position() > 0)
{
mBuffer.rewind();
ReusableComplexBuffer reusableComplexBuffer = mReusableComplexBufferQueue.getBuffer(mBufferSize);
reusableComplexBuffer.reloadFrom(mBuffer, mCurrentBufferTimestamp);
reusableComplexBuffer.incrementUserCount();
mListener.receive(reusableComplexBuffer);
}
mBuffer.rewind();
mCurrentBufferTimestamp = mTimestampManager.getCurrentTimestamp();
}
/**
* Sets the listener to receive the output from this buffer assembler. Note: the listener is responsible for
* managing the user count for any buffers received from this buffer assembler.
*/
public void setListener(Listener<ReusableComplexBuffer> listener)
{
mListener = listener;
}
}

View File

@@ -1,35 +0,0 @@
package io.github.dsheirer.sample.complex;
import io.github.dsheirer.sample.Listener;
/**
* Real sample buffer to stream converter
*/
public class ComplexBufferToStreamConverter implements Listener<ComplexBuffer>
{
private ComplexSampleListener mListener;
public void dispose()
{
mListener = null;
}
@Override
public void receive( ComplexBuffer buffer )
{
if( mListener != null )
{
float[] samples = buffer.getSamples();
for( int x = 0; x < samples.length; x += 2 )
{
mListener.receive( samples[ x ], samples[ x + 1 ] );
}
}
}
public void setListener( ComplexSampleListener listener )
{
mListener = listener;
}
}

View File

@@ -1,81 +0,0 @@
/*******************************************************************************
* 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 io.github.dsheirer.sample.complex;
import io.github.dsheirer.sample.Listener;
import org.apache.commons.lang3.Validate;
/**
* Assembles complex float samples into a ComplexBuffer containing an array of floats
*/
public class ComplexToComplexBufferAssembler implements ComplexSampleListener
{
private int mBufferSize;
private float[] mBuffer;
private int mBufferPointer;
private Listener<ComplexBuffer> mListener;
public ComplexToComplexBufferAssembler( int bufferSize )
{
Validate.isTrue(bufferSize % 2 == 0);
mBufferSize = bufferSize;
mBuffer = new float[ mBufferSize ];
}
public void dispose()
{
mListener = null;
mBuffer = null;
}
public void reset()
{
mBuffer = new float[ mBufferSize ];
mBufferPointer = 0;
}
@Override
public void receive( float i, float q )
{
if( mBufferPointer >= mBufferSize )
{
if( mListener != null )
{
mListener.receive( new ComplexBuffer( mBuffer ) );
}
mBuffer = new float[ mBufferSize ];
mBufferPointer = 0;
}
mBuffer[ mBufferPointer ] = i;
mBufferPointer++;
mBuffer[ mBufferPointer ] = q;
mBufferPointer++;
}
public void setListener( Listener<ComplexBuffer> listener )
{
mListener = listener;
}
}

View File

@@ -1,55 +0,0 @@
package io.github.dsheirer.sample.complex;
import io.github.dsheirer.sample.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RateCounter_CB implements Listener<ComplexBuffer>
{
private final static Logger mLog =
LoggerFactory.getLogger( RateCounter_CB.class );
private Listener<ComplexBuffer> mListener;
private long mCount;
private long mStartTime = -1;
private String mName;
public RateCounter_CB()
{
mName = "UNNAMED";
}
public RateCounter_CB( String name )
{
mName = name;
}
@Override
public void receive( ComplexBuffer buffer )
{
if( mStartTime == -1 )
{
mStartTime = System.currentTimeMillis();
}
mCount += ( buffer.getSamples().length / 2 );
long elapsed = System.currentTimeMillis() - mStartTime;
if( elapsed != 0 )
{
mLog.debug( "[" + mName + "] Sample Rate: " + ( mCount / elapsed ) + " kHz" );
}
if( mListener != null )
{
mListener.receive( buffer );
}
}
public void setListener( Listener<ComplexBuffer> listener )
{
mListener = listener;
}
}

View File

@@ -1,8 +0,0 @@
package io.github.dsheirer.sample.real;
import io.github.dsheirer.sample.Listener;
public interface IFilteredRealBufferListener
{
public Listener<RealBuffer> getFilteredRealBufferListener();
}

View File

@@ -1,9 +0,0 @@
package io.github.dsheirer.sample.real;
import io.github.dsheirer.sample.Listener;
public interface IFilteredRealBufferProvider
{
public void setFilteredRealBufferListener( Listener<RealBuffer> listener );
public void removeFilteredRealBufferListener();
}

View File

@@ -1,28 +0,0 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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 io.github.dsheirer.sample.real;
public interface IOverflowListener
{
/**
* Indicates that the source is in overflow state (true) or out of overflow state (false)
* @param overflow true to indicate an overflow state
*/
void sourceOverflow(boolean overflow);
}

View File

@@ -1,8 +0,0 @@
package io.github.dsheirer.sample.real;
import io.github.dsheirer.sample.Listener;
public interface IUnFilteredRealBufferListener
{
public Listener<RealBuffer> getUnFilteredRealBufferListener();
}

View File

@@ -1,9 +0,0 @@
package io.github.dsheirer.sample.real;
import io.github.dsheirer.sample.Listener;
public interface IUnFilteredRealBufferProvider
{
public void setUnFilteredRealBufferListener( Listener<RealBuffer> listener );
public void removeUnFilteredRealBufferListener();
}

View File

@@ -1,41 +0,0 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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 io.github.dsheirer.sample.real;
import io.github.dsheirer.sample.Buffer;
import java.util.Arrays;
public class RealBuffer extends Buffer
{
/**
* Wrapper around float array containing real float samples
*/
public RealBuffer(float[] samples)
{
super(samples);
}
/**
* Creates a deep copy of the buffer and a shallow (reference) copy of the source event
*/
public RealBuffer copyOf()
{
float[] copy = Arrays.copyOf(getSamples(), getSamples().length);
return new RealBuffer(copy);
}
}

View File

@@ -1,72 +0,0 @@
/*******************************************************************************
* 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 io.github.dsheirer.sample.real;
import io.github.dsheirer.sample.Listener;
/**
* Assembles single float samples into a RealBuffer containing an array of floats
*/
public class RealToRealBufferAssembler implements RealSampleListener
{
private int mBufferSize;
private float[] mBuffer;
private int mBufferPointer;
private Listener<RealBuffer> mListener;
public RealToRealBufferAssembler(int bufferSize)
{
mBufferSize = bufferSize;
mBuffer = new float[mBufferSize];
}
public void dispose()
{
mListener = null;
mBuffer = null;
}
public void reset()
{
mBuffer = new float[mBufferSize];
mBufferPointer = 0;
}
@Override
public void receive(float sample)
{
mBuffer[mBufferPointer++] = sample;
if (mBufferPointer >= mBufferSize)
{
if (mListener != null)
{
mListener.receive(new RealBuffer(mBuffer));
}
mBuffer = new float[mBufferSize];
mBufferPointer = 0;
}
}
public void setListener(Listener<RealBuffer> listener)
{
mListener = listener;
}
}

View File

@@ -18,8 +18,8 @@
package io.github.dsheirer.source;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.sample.IOverflowListener;
import io.github.dsheirer.sample.SampleType;
import io.github.dsheirer.sample.real.IOverflowListener;
import io.github.dsheirer.source.heartbeat.HeartbeatManager;
import io.github.dsheirer.source.heartbeat.IHeartbeatProvider;

View File

@@ -38,7 +38,7 @@ public class ComplexMixer
* @param targetDataLine - mixer or sound card to be used
* @param format - audio format
* @param name - token name to use for this mixer
* @param complexSampleAdapter - adapter to convert byte array data read from the mixer into ComplexBuffer.
* @param complexSampleAdapter - adapter to convert byte array data read from the mixer into ReusableComplexBuffer.
*/
public ComplexMixer(TargetDataLine targetDataLine, AudioFormat format, String name,
ComplexSampleAdapter complexSampleAdapter)

View File

@@ -20,11 +20,11 @@ import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.cic.ComplexPrimeCICDecimate;
import io.github.dsheirer.dsp.mixer.IOscillator;
import io.github.dsheirer.dsp.mixer.Oscillator;
import io.github.dsheirer.sample.Buffer;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.OverflowableTransferQueue;
import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
import io.github.dsheirer.sample.complex.Complex;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.source.tuner.Tuner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -45,12 +45,12 @@ public class CICTunerChannelSource
private static double CHANNEL_RATE = 48000.0;
private static int CHANNEL_PASS_FREQUENCY = 12000;
private OverflowableTransferQueue<ComplexBuffer> mBuffer;
private OverflowableReusableBufferTransferQueue<ReusableComplexBuffer> mBuffer;
private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("CICTunerChannelSource");
private IOscillator mFrequencyCorrectionMixer;
private ComplexPrimeCICDecimate mDecimationFilter;
private Listener<ComplexBuffer> mListener;
private List<ComplexBuffer> mSampleBuffers = new ArrayList<ComplexBuffer>();
private Listener<ReusableComplexBuffer> mListener;
private List<ReusableComplexBuffer> mSampleBuffers = new ArrayList<>();
private long mTunerFrequency = 0;
private double mTunerSampleRate;
private long mChannelFrequencyCorrection = 0;
@@ -68,7 +68,7 @@ public class CICTunerChannelSource
*/
public CICTunerChannelSource(Tuner tuner, TunerChannel tunerChannel)
{
mBuffer = new OverflowableTransferQueue<>(BUFFER_MAX_CAPACITY, BUFFER_OVERFLOW_RESET_THRESHOLD);
mBuffer = new OverflowableReusableBufferTransferQueue<>(BUFFER_MAX_CAPACITY, BUFFER_OVERFLOW_RESET_THRESHOLD);
// mBuffer.setSourceOverflowListener(this);
//Setup the frequency translator to the current source frequency
@@ -120,12 +120,12 @@ public class CICTunerChannelSource
* Primary input method for receiving complex sample buffers from the wideband source (ie tuner)
* @param buffer to receive and eventually process
*/
public void receive(ComplexBuffer buffer)
public void receive(ReusableComplexBuffer buffer)
{
mBuffer.offer(buffer);
}
public void setListener(Listener<ComplexBuffer> listener)
public void setListener(Listener<ReusableComplexBuffer> listener)
{
/* Save a pointer to the listener so that if we have to change the
* decimation filter, we can re-add the listener */
@@ -134,7 +134,7 @@ public class CICTunerChannelSource
mDecimationFilter.setListener(listener);
}
public void removeListener(Listener<ComplexBuffer> listener)
public void removeListener(Listener<ReusableComplexBuffer> listener)
{
mDecimationFilter.removeListener();
}
@@ -181,14 +181,13 @@ public class CICTunerChannelSource
{
mBuffer.drainTo(mSampleBuffers, 20);
for(Buffer buffer : mSampleBuffers)
for(ReusableComplexBuffer buffer : mSampleBuffers)
{
float[] samples = buffer.getSamples();
/* We make a copy of the buffer so that we don't affect
* anyone else that is using the same buffer, like other
* channels or the spectral display */
float[] translated = new float[samples.length];
ReusableComplexBuffer reusableComplexBuffer = mReusableComplexBufferQueue.getBuffer(samples.length);
reusableComplexBuffer.incrementUserCount();
float[] translated = reusableComplexBuffer.getSamples();
/* Perform frequency translation */
for(int x = 0; x < samples.length; x += 2)
@@ -206,8 +205,10 @@ public class CICTunerChannelSource
if(filter != null)
{
filter.receive(new ComplexBuffer(translated));
filter.receive(reusableComplexBuffer);
}
buffer.decrementUserCount();
}
mSampleBuffers.clear();

View File

@@ -21,12 +21,13 @@ import io.github.dsheirer.controller.channel.ChannelEventListener;
import io.github.dsheirer.controller.channel.ChannelProcessingManager;
import io.github.dsheirer.dsp.filter.Filters;
import io.github.dsheirer.dsp.filter.Window.WindowType;
import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter_RB_RB;
import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter;
import io.github.dsheirer.dsp.filter.smoothing.SmoothingFilter.SmoothingType;
import io.github.dsheirer.module.ProcessingChain;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.SampleType;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
import io.github.dsheirer.settings.ColorSetting.ColorSettingName;
import io.github.dsheirer.settings.ColorSettingMenuItem;
@@ -74,7 +75,7 @@ public class ChannelSpectrumPanel extends JPanel implements ChannelEventListener
private int mSampleBufferSize = 2400;
private HalfBandFilter_RB_RB mDecimatingFilter = new HalfBandFilter_RB_RB(
private HalfBandFilter mDecimatingFilter = new HalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 1.0f, true);
private AtomicBoolean mEnabled = new AtomicBoolean();
@@ -215,8 +216,7 @@ public class ChannelSpectrumPanel extends JPanel implements ChannelEventListener
if(processingChain != null)
{
// processingChain.addRealBufferListener(this);
processingChain.addDemodulatedAudioListener(this);
mDFTProcessor.start();
}
}
@@ -226,12 +226,11 @@ public class ChannelSpectrumPanel extends JPanel implements ChannelEventListener
{
if(mCurrentChannel != null && mCurrentChannel.isProcessing())
{
ProcessingChain processingChain = mChannelProcessingManager
.getProcessingChain(mCurrentChannel);
ProcessingChain processingChain = mChannelProcessingManager.getProcessingChain(mCurrentChannel);
if(processingChain != null)
{
// processingChain.removeRealBufferListener(this);
processingChain.removeDemodulatedAudioListener(this);
}
}
@@ -270,15 +269,14 @@ public class ChannelSpectrumPanel extends JPanel implements ChannelEventListener
@Override
public void receive(ReusableBuffer buffer)
{
throw new IllegalStateException("This must be updated to work with reusable buffers");
// RealBuffer decimated = mDecimatingFilter.filter(buffer);
//
// //Hack: we're placing real samples in a complex buffer that the DFT
// //processor is expecting.
// ReusableComplexBuffer reusableComplexBuffer = mReusableComplexBufferQueue.getBuffer(decimated.getSamples().length);
// reusableComplexBuffer.reloadFrom(decimated.getSamples(), System.currentTimeMillis());
// reusableComplexBuffer.incrementUserCount();
// mDFTProcessor.receive(reusableComplexBuffer);
ReusableBuffer decimated = mDecimatingFilter.filter(buffer);
//Hack: we're placing real samples in a complex buffer that the DFT
//processor is expecting.
ReusableComplexBuffer reusableComplexBuffer = mReusableComplexBufferQueue.getBuffer(decimated.getSamples().length);
reusableComplexBuffer.reloadFrom(decimated.getSamples(), System.currentTimeMillis());
reusableComplexBuffer.incrementUserCount();
mDFTProcessor.receive(reusableComplexBuffer);
}
/**

View File

@@ -18,10 +18,10 @@ package io.github.dsheirer.spectrum;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.Window.WindowType;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.sample.IOverflowListener;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.SampleType;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.real.IOverflowListener;
import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.spectrum.converter.DFTResultsConverter;