mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
[WIP] Removes all usage of Buffer, RealBuffer and ComplexBuffer.
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package io.github.dsheirer.sample.real;
|
||||
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
|
||||
public interface IFilteredRealBufferListener
|
||||
{
|
||||
public Listener<RealBuffer> getFilteredRealBufferListener();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package io.github.dsheirer.sample.real;
|
||||
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
|
||||
public interface IUnFilteredRealBufferListener
|
||||
{
|
||||
public Listener<RealBuffer> getUnFilteredRealBufferListener();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user