mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-14 17:47:32 -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.config.DecodeConfiguration;
|
||||||
import io.github.dsheirer.module.decode.event.CallEvent;
|
import io.github.dsheirer.module.decode.event.CallEvent;
|
||||||
import io.github.dsheirer.module.decode.event.ICallEventProvider;
|
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.Listener;
|
||||||
import io.github.dsheirer.sample.real.IOverflowListener;
|
|
||||||
import io.github.dsheirer.source.ISourceEventListener;
|
import io.github.dsheirer.source.ISourceEventListener;
|
||||||
import io.github.dsheirer.source.SourceEvent;
|
import io.github.dsheirer.source.SourceEvent;
|
||||||
import io.github.dsheirer.source.heartbeat.Heartbeat;
|
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.FilterFactory;
|
||||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
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.Listener;
|
||||||
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
|
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
|
||||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||||
import io.github.dsheirer.sample.real.IOverflowListener;
|
|
||||||
import org.jtransforms.fft.FloatFFT_1D;
|
import org.jtransforms.fft.FloatFFT_1D;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|||||||
@@ -15,9 +15,9 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package io.github.dsheirer.dsp.filter.channelizer;
|
package io.github.dsheirer.dsp.filter.channelizer;
|
||||||
|
|
||||||
|
import io.github.dsheirer.sample.IOverflowListener;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.sample.OverflowableTransferQueue;
|
import io.github.dsheirer.sample.OverflowableTransferQueue;
|
||||||
import io.github.dsheirer.sample.real.IOverflowListener;
|
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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.dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor;
|
||||||
import io.github.dsheirer.sample.Listener;
|
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.ReusableChannelResultsBuffer;
|
||||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
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.SourceEvent;
|
||||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
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 final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class);
|
||||||
|
|
||||||
private ReusableBufferAssembler mReusableBufferAssembler;
|
private ReusableComplexBufferAssembler mReusableComplexBufferAssembler;
|
||||||
private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor;
|
private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor;
|
||||||
private double mChannelSampleRate;
|
private double mChannelSampleRate;
|
||||||
private long mIndexCenterFrequency;
|
private long mIndexCenterFrequency;
|
||||||
@@ -54,7 +54,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
|||||||
super(producerSourceEventListener, tunerChannel);
|
super(producerSourceEventListener, tunerChannel);
|
||||||
mPolyphaseChannelOutputProcessor = outputProcessor;
|
mPolyphaseChannelOutputProcessor = outputProcessor;
|
||||||
mChannelSampleRate = channelSampleRate;
|
mChannelSampleRate = channelSampleRate;
|
||||||
mReusableBufferAssembler = new ReusableBufferAssembler(2500, mChannelSampleRate);
|
mReusableComplexBufferAssembler = new ReusableComplexBufferAssembler(2500, mChannelSampleRate);
|
||||||
setFrequency(centerFrequency);
|
setFrequency(centerFrequency);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
|||||||
@Override
|
@Override
|
||||||
public void setListener(final Listener<ReusableComplexBuffer> listener)
|
public void setListener(final Listener<ReusableComplexBuffer> listener)
|
||||||
{
|
{
|
||||||
mReusableBufferAssembler.setListener(listener);
|
mReusableComplexBufferAssembler.setListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,7 +73,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
|||||||
@Override
|
@Override
|
||||||
public void removeListener(Listener<ReusableComplexBuffer> listener)
|
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
|
//Fully process the residual channel results buffer of the previous channel output processor
|
||||||
if(existingProcessor != null)
|
if(existingProcessor != null)
|
||||||
{
|
{
|
||||||
existingProcessor.processChannelResults(mReusableBufferAssembler);
|
existingProcessor.processChannelResults(mReusableComplexBufferAssembler);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Finally, setup the frequency offset for the output processor and reset the frequency correction value
|
//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)
|
public void receiveChannelResults(ReusableChannelResultsBuffer channelResultsBuffer)
|
||||||
{
|
{
|
||||||
mReusableBufferAssembler.updateTimestamp(channelResultsBuffer.getTimestamp());
|
mReusableComplexBufferAssembler.updateTimestamp(channelResultsBuffer.getTimestamp());
|
||||||
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsBuffer);
|
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,9 +161,9 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
|||||||
@Override
|
@Override
|
||||||
public void dispose()
|
public void dispose()
|
||||||
{
|
{
|
||||||
if(mReusableBufferAssembler != null)
|
if(mReusableComplexBufferAssembler != null)
|
||||||
{
|
{
|
||||||
mReusableBufferAssembler.dispose();
|
mReusableComplexBufferAssembler.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mPolyphaseChannelOutputProcessor != null)
|
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
|
//Lock on the output processor so that it can't be changed in the middle of processing
|
||||||
synchronized(mPolyphaseChannelOutputProcessor)
|
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.IOscillator;
|
||||||
import io.github.dsheirer.dsp.mixer.Oscillator;
|
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.OverflowableReusableBufferTransferQueue;
|
||||||
import io.github.dsheirer.sample.buffer.ReusableBufferAssembler;
|
|
||||||
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
|
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 io.github.dsheirer.source.Source;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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
|
* 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
|
@Override
|
||||||
public void processChannelResults(ReusableBufferAssembler reusableBufferAssembler)
|
public void processChannelResults(ReusableComplexBufferAssembler reusableComplexBufferAssembler)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -110,7 +110,7 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
|
|||||||
|
|
||||||
if(toProcess > 0)
|
if(toProcess > 0)
|
||||||
{
|
{
|
||||||
process(mChannelResultsToProcess, reusableBufferAssembler);
|
process(mChannelResultsToProcess, reusableComplexBufferAssembler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(Throwable throwable)
|
catch(Throwable throwable)
|
||||||
@@ -124,10 +124,10 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
|
|||||||
/**
|
/**
|
||||||
* Sub-class implementation to process one polyphase channelizer result array.
|
* Sub-class implementation to process one polyphase channelizer result array.
|
||||||
* @param channelResults to process
|
* @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,
|
public abstract void process(List<ReusableChannelResultsBuffer> channelResults,
|
||||||
ReusableBufferAssembler reusableBufferAssembler);
|
ReusableComplexBufferAssembler reusableComplexBufferAssembler);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -18,8 +18,8 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package io.github.dsheirer.dsp.filter.channelizer.output;
|
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.ReusableChannelResultsBuffer;
|
||||||
|
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
|
||||||
import io.github.dsheirer.source.Source;
|
import io.github.dsheirer.source.Source;
|
||||||
|
|
||||||
import java.util.List;
|
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
|
* 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
|
* 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;
|
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.ReusableChannelResultsBuffer;
|
||||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||||
|
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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.
|
* 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 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
|
@Override
|
||||||
public void process(List<ReusableChannelResultsBuffer> channelResults,
|
public void process(List<ReusableChannelResultsBuffer> channelResults,
|
||||||
ReusableBufferAssembler reusableBufferAssembler)
|
ReusableComplexBufferAssembler reusableComplexBufferAssembler)
|
||||||
{
|
{
|
||||||
for(ReusableChannelResultsBuffer channelResultsBuffer: channelResults)
|
for(ReusableChannelResultsBuffer channelResultsBuffer: channelResults)
|
||||||
{
|
{
|
||||||
@@ -91,7 +91,7 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
|
|||||||
getFrequencyCorrectionMixer().mixComplex(channelBuffer.getSamples());
|
getFrequencyCorrectionMixer().mixComplex(channelBuffer.getSamples());
|
||||||
}
|
}
|
||||||
|
|
||||||
reusableBufferAssembler.receive(channelBuffer);
|
reusableComplexBufferAssembler.receive(channelBuffer);
|
||||||
}
|
}
|
||||||
catch(IllegalArgumentException iae)
|
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.complex.ComplexFIRFilter2;
|
||||||
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
||||||
import io.github.dsheirer.dsp.mixer.FS4DownConverter;
|
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.ReusableChannelResultsBuffer;
|
||||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||||
|
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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.
|
* 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 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
|
@Override
|
||||||
public void process(List<ReusableChannelResultsBuffer> channelResultsBuffers,
|
public void process(List<ReusableChannelResultsBuffer> channelResultsBuffers,
|
||||||
ReusableBufferAssembler reusableBufferAssembler)
|
ReusableComplexBufferAssembler reusableComplexBufferAssembler)
|
||||||
{
|
{
|
||||||
for(ReusableChannelResultsBuffer buffer: channelResultsBuffers)
|
for(ReusableChannelResultsBuffer buffer: channelResultsBuffers)
|
||||||
{
|
{
|
||||||
@@ -141,7 +141,7 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
|
|||||||
|
|
||||||
ReusableComplexBuffer lowPassFiltered = mLowPassFilter.filter(synthesized);
|
ReusableComplexBuffer lowPassFiltered = mLowPassFilter.filter(synthesized);
|
||||||
|
|
||||||
reusableBufferAssembler.receive(lowPassFiltered);
|
reusableComplexBufferAssembler.receive(lowPassFiltered);
|
||||||
|
|
||||||
buffer.decrementUserCount();
|
buffer.decrementUserCount();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,16 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* sdrtrunk
|
* sdr-trunk
|
||||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||||
* it under the terms of the GNU General Public License as published by
|
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* later version.
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
* 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
|
* You should have received a copy of the GNU General Public License along with this program.
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* If not, see <http://www.gnu.org/licenses/>
|
||||||
*
|
*
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package io.github.dsheirer.dsp.filter.cic;
|
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.FilterFactory;
|
||||||
import io.github.dsheirer.dsp.filter.Filters;
|
import io.github.dsheirer.dsp.filter.Filters;
|
||||||
import io.github.dsheirer.dsp.filter.Window;
|
import io.github.dsheirer.dsp.filter.Window;
|
||||||
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
|
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2;
|
||||||
import io.github.dsheirer.dsp.filter.halfband.complex.HalfBandFilter_CB_CB;
|
import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandFilter;
|
||||||
import io.github.dsheirer.sample.Listener;
|
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.ComplexSampleListener;
|
||||||
import io.github.dsheirer.sample.complex.ComplexToComplexBufferAssembler;
|
|
||||||
import io.github.dsheirer.sample.decimator.ComplexDecimator;
|
import io.github.dsheirer.sample.decimator.ComplexDecimator;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
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,
|
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,
|
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
|
else
|
||||||
{
|
{
|
||||||
/* Wire the current stage to the previous stage */
|
/* Wire the current stage to the previous stage */
|
||||||
mDecimatingStages.get(x - 1).setListener(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
|
* 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);
|
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.
|
* Primary input method for receiving sample arrays composed as I,Q,I,Q, etc.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void receive(ComplexBuffer buffer)
|
public void receive(ReusableComplexBuffer buffer)
|
||||||
{
|
{
|
||||||
if(mFirstDecimatingStage != null)
|
if(mFirstDecimatingStage != null)
|
||||||
{
|
{
|
||||||
@@ -177,6 +174,8 @@ public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
|
|||||||
mFirstDecimatingStage.receive(samples[x], samples[x + 1]);
|
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)
|
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;
|
mISum = mISum - mISamples[mSamplePointer] + i;
|
||||||
mQSum = mQSum - mQSamples[mSamplePointer] + q;
|
mQSum = mQSum - mQSamples[mSamplePointer] + q;
|
||||||
|
|
||||||
/* Overwrite the oldest sample with the newest */
|
/* Overwrite the oldest sample with the newest */
|
||||||
mISamples[mSamplePointer] = i;
|
mISamples[mSamplePointer] = i;
|
||||||
mQSamples[mSamplePointer] = q;
|
mQSamples[mSamplePointer] = q;
|
||||||
|
|
||||||
@@ -352,27 +351,37 @@ public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
|
|||||||
public class Output implements ComplexSampleListener
|
public class Output implements ComplexSampleListener
|
||||||
{
|
{
|
||||||
/* Decimated output buffers will contain 1024 samples */
|
/* Decimated output buffers will contain 1024 samples */
|
||||||
private ComplexToComplexBufferAssembler mAssembler =
|
private ReusableComplexBufferAssembler mAssembler = new ReusableComplexBufferAssembler(2048, 48000.0);
|
||||||
new ComplexToComplexBufferAssembler(2048);
|
|
||||||
|
|
||||||
private ComplexFIRFilter_CB_CB mCleanupFilter;
|
private ComplexFIRFilter2 mCleanupFilter;
|
||||||
private HalfBandFilter_CB_CB mHalfBandFilter = new HalfBandFilter_CB_CB(
|
private ComplexHalfBandFilter mHalfBandFilter = new ComplexHalfBandFilter(
|
||||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false);
|
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false);
|
||||||
|
private Listener<ReusableComplexBuffer> mReusableComplexBufferListener;
|
||||||
|
|
||||||
public Output(int outputSampleRate, int passFrequency, int attenuation,
|
public Output(int outputSampleRate, int passFrequency, int attenuation,
|
||||||
Window.WindowType windowType)
|
Window.WindowType windowType)
|
||||||
{
|
{
|
||||||
mCleanupFilter = new ComplexFIRFilter_CB_CB(FilterFactory
|
mCleanupFilter = new ComplexFIRFilter2(FilterFactory
|
||||||
.getCICCleanupFilter(outputSampleRate,
|
.getCICCleanupFilter(outputSampleRate, passFrequency, attenuation, windowType), 0.4f);
|
||||||
passFrequency,
|
|
||||||
attenuation,
|
|
||||||
windowType), 0.4f);
|
|
||||||
|
|
||||||
//Bypassing the CIC cleanup filter for now
|
//Bypassing the CIC cleanup filter for now
|
||||||
mAssembler.setListener(mHalfBandFilter);
|
mAssembler.setListener(new Listener<ReusableComplexBuffer>()
|
||||||
|
{
|
||||||
// mAssembler.setListener( mCleanupFilter );
|
@Override
|
||||||
// mCleanupFilter.setListener( mHalfBandFilter );
|
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()
|
public void dispose()
|
||||||
@@ -390,23 +399,17 @@ public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
|
|||||||
@Override
|
@Override
|
||||||
public void receive(float inphase, float quadrature)
|
public void receive(float inphase, float quadrature)
|
||||||
{
|
{
|
||||||
mAssembler.receive(inphase, quadrature);
|
mAssembler.receive(new float[]{inphase, quadrature});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void setListener(Listener<ReusableComplexBuffer> listener)
|
||||||
* Adds a listener to receive output samples
|
|
||||||
*/
|
|
||||||
public void setListener(Listener<ComplexBuffer> listener)
|
|
||||||
{
|
{
|
||||||
mHalfBandFilter.setListener(listener);
|
mReusableComplexBufferListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the listener from receiving output samples
|
|
||||||
*/
|
|
||||||
public void removeListener()
|
public void removeListener()
|
||||||
{
|
{
|
||||||
mHalfBandFilter.removeListener();
|
mReusableComplexBufferListener = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ package io.github.dsheirer.dsp.filter.cic;
|
|||||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||||
import io.github.dsheirer.dsp.filter.Filters;
|
import io.github.dsheirer.dsp.filter.Filters;
|
||||||
import io.github.dsheirer.dsp.filter.Window.WindowType;
|
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.fir.real.RealFIRFilter2;
|
||||||
import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter_RB_RB;
|
import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter;
|
||||||
import io.github.dsheirer.sample.Listener;
|
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.decimator.RealDecimator;
|
||||||
import io.github.dsheirer.sample.real.RealBuffer;
|
|
||||||
import io.github.dsheirer.sample.real.RealSampleListener;
|
import io.github.dsheirer.sample.real.RealSampleListener;
|
||||||
import io.github.dsheirer.sample.real.RealToRealBufferAssembler;
|
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -36,379 +36,387 @@ import java.util.List;
|
|||||||
|
|
||||||
public class RealPrimeCICDecimate
|
public class RealPrimeCICDecimate
|
||||||
{
|
{
|
||||||
private final static Logger mLog =
|
private final static Logger mLog = LoggerFactory.getLogger(RealPrimeCICDecimate.class);
|
||||||
LoggerFactory.getLogger( RealPrimeCICDecimate.class );
|
|
||||||
|
|
||||||
public static int[] PRIMES = { 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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 };
|
599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691};
|
||||||
|
|
||||||
private ArrayList<DecimatingStage> mDecimatingStages =
|
private List<DecimatingStage> mDecimatingStages = new ArrayList<DecimatingStage>();
|
||||||
new ArrayList<DecimatingStage>();
|
private DecimatingStage mFirstDecimatingStage;
|
||||||
|
private Output mOutput;
|
||||||
|
|
||||||
private DecimatingStage mFirstDecimatingStage;
|
private int mOutputBufferSize = 2048;
|
||||||
|
|
||||||
private Output mOutput;
|
/**
|
||||||
|
* Non-Recursive Prime-Factor CIC Filter with float sample array inputs and
|
||||||
private int mOutputBufferSize = 2048;
|
* decimated float sample output.
|
||||||
|
*
|
||||||
/**
|
* Implements the CIC filter described in Understanding Digital Signal
|
||||||
* Non-Recursive Prime-Factor CIC Filter with float sample array inputs and
|
* Processing, 3e, Lyons, on page 769. This filter is comprised of multiple
|
||||||
* decimated float sample output.
|
* decimating stages each with a prime factor decimation rate. Multiple
|
||||||
*
|
* stages are cascaded to achieve the overall decimation rate.
|
||||||
* Implements the CIC filter described in Understanding Digital Signal
|
*
|
||||||
* Processing, 3e, Lyons, on page 769. This filter is comprised of multiple
|
* This filter supports a maximum decimation rate of 700. This filter can
|
||||||
* decimating stages each with a prime factor decimation rate. Multiple
|
* be adapted to higher decimation rates by adding additional prime factors
|
||||||
* stages are cascaded to achieve the overall decimation rate.
|
* to the PRIMES array.
|
||||||
*
|
*
|
||||||
* This filter supports a maximum decimation rate of 700. This filter can
|
* @param decimation - overall decimation rate
|
||||||
* be adapted to higher decimation rates by adding additional prime factors
|
* @param order - filter order
|
||||||
* to the PRIMES array.
|
*/
|
||||||
*
|
public RealPrimeCICDecimate(int decimation, int order, int passFrequency, int attenuation, WindowType windowType)
|
||||||
* @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);
|
|
||||||
|
|
||||||
mOutputBufferSize = outputBufferSize;
|
|
||||||
|
|
||||||
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 )
|
|
||||||
{
|
{
|
||||||
if( mFirstDecimatingStage != null )
|
this(decimation, order, passFrequency, attenuation, windowType, 2048);
|
||||||
{
|
|
||||||
for( float sample: buffer.getSamples() )
|
|
||||||
{
|
|
||||||
mFirstDecimatingStage.receive( sample );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public RealPrimeCICDecimate(int decimation, int order, int passFrequency, int attenuation, WindowType windowType, int outputBufferSize)
|
||||||
* Decimating stage combines multiple CIC stages with a decimator. The
|
{
|
||||||
* number of stages is indicated by the order value and the size indicates
|
Validate.isTrue(decimation <= 700);
|
||||||
* 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 )
|
mOutputBufferSize = outputBufferSize;
|
||||||
{
|
|
||||||
for( int x = 0; x < order; x++ )
|
|
||||||
{
|
|
||||||
Stage stage;
|
|
||||||
|
|
||||||
if( size == 2 )
|
List<Integer> stageSizes = getPrimeFactors(decimation);
|
||||||
{
|
|
||||||
stage = new TwoStage();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
stage = new Stage( size );
|
|
||||||
}
|
|
||||||
|
|
||||||
mStages.add( stage );
|
for(int x = 0; x < stageSizes.size(); x++)
|
||||||
|
|
||||||
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 );
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setListener( RealSampleListener listener )
|
mOutput = new Output(48000, passFrequency, attenuation, windowType,
|
||||||
{
|
outputBufferSize);
|
||||||
mDecimator.setListener( listener );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
mDecimatingStages.get(mDecimatingStages.size() - 1)
|
||||||
* Single non-decimating CIC stage component. Uses a circular buffer and a
|
.setListener(mOutput);
|
||||||
* 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;
|
public void dispose()
|
||||||
|
{
|
||||||
protected float mSum;
|
for(DecimatingStage stage : mDecimatingStages)
|
||||||
|
|
||||||
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 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 );
|
stage.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
mDecimatingStages.clear();
|
||||||
* Adds a listener to receive output samples
|
mDecimatingStages = null;
|
||||||
*/
|
mFirstDecimatingStage = null;
|
||||||
public void setListener( Listener<RealBuffer> listener )
|
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
mHalfBandFilter.setListener( listener );
|
int prime = PRIMES[pointer];
|
||||||
|
|
||||||
|
if(decimation % prime == 0)
|
||||||
|
{
|
||||||
|
stages.add(prime);
|
||||||
|
|
||||||
|
decimation /= prime;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pointer++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
return stages;
|
||||||
* Removes the listener from receiving output samples
|
}
|
||||||
*/
|
|
||||||
|
/**
|
||||||
|
* 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()
|
public void removeListener()
|
||||||
{
|
{
|
||||||
mHalfBandFilter.removeListener();
|
mReusableBufferListener = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,13 +18,15 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package io.github.dsheirer.dsp.filter.correction;
|
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
|
public class IQCorrectionFilter
|
||||||
{
|
{
|
||||||
private float mRatio = 0.00001f;
|
private float mRatio = 0.00001f;
|
||||||
private float mAverageInphase = 0.0f;
|
private float mAverageInphase = 0.0f;
|
||||||
private float mAverageQuadrature = 0.0f;
|
private float mAverageQuadrature = 0.0f;
|
||||||
|
private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("IQCorrectionFilter");
|
||||||
|
|
||||||
public IQCorrectionFilter(float ratio)
|
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();
|
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)
|
for(int x = 0; x < samples.length; x += 2)
|
||||||
{
|
{
|
||||||
mAverageInphase = mAverageInphase + (mRatio * (samples[x] - mAverageInphase));
|
mAverageInphase = mAverageInphase + (mRatio * (samples[x] - mAverageInphase));
|
||||||
samples[x] -= mAverageInphase;
|
filteredSamples[x] = samples[x] - mAverageInphase;
|
||||||
|
|
||||||
mAverageQuadrature = mAverageQuadrature + (mRatio * (samples[x + 1] - mAverageQuadrature));
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,146 +17,173 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package io.github.dsheirer.dsp.filter.halfband.real;
|
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;
|
import org.apache.commons.lang3.Validate;
|
||||||
|
|
||||||
public class HalfBandFilter
|
public class HalfBandFilter
|
||||||
{
|
{
|
||||||
private float[] mBuffer;
|
private float[] mBuffer;
|
||||||
private int mBufferSize = 1; //Temporary initial value
|
private int mBufferSize = 1; //Temporary initial value
|
||||||
private int mBufferPointer = 0;
|
private int mBufferPointer = 0;
|
||||||
|
|
||||||
private float mGain;
|
private float mGain;
|
||||||
private float[] mCoefficients;
|
private float[] mCoefficients;
|
||||||
private int[][] mIndexMap;
|
private int[][] mIndexMap;
|
||||||
|
|
||||||
private int mCenterCoefficient;
|
private int mCenterCoefficient;
|
||||||
private int mCenterCoefficientMapIndex;
|
private int mCenterCoefficientMapIndex;
|
||||||
|
|
||||||
/**
|
private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("HalfBandFilter");
|
||||||
* 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 ];
|
* 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
|
||||||
|
|
||||||
generateIndexMap( mCoefficients.length );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dispose()
|
mCoefficients = coefficients;
|
||||||
{
|
mGain = gain;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
mBufferSize = mCoefficients.length;
|
||||||
* Inserts the sample into the buffer without calculating a filtered value
|
mBuffer = new float[mBufferSize];
|
||||||
*/
|
|
||||||
public void insert( float sample )
|
|
||||||
{
|
|
||||||
mBuffer[ mBufferPointer ] = sample;
|
|
||||||
|
|
||||||
mBufferPointer++;
|
generateIndexMap(mCoefficients.length);
|
||||||
|
}
|
||||||
|
|
||||||
mBufferPointer = mBufferPointer % mBufferSize;
|
public HalfBandFilter(float[] coefficients, float gain, boolean decimate)
|
||||||
}
|
{
|
||||||
|
this(coefficients, gain);
|
||||||
|
|
||||||
/**
|
throw new IllegalStateException("This filter must be updated to support non-decimate option");
|
||||||
* Calculate the filtered value by applying the coefficients against
|
}
|
||||||
* the complex samples in mBuffer
|
|
||||||
*/
|
|
||||||
public float filter( float sample )
|
|
||||||
{
|
|
||||||
insert( sample );
|
|
||||||
|
|
||||||
float accumulator = 0;
|
public void dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
//Start with the center tap value
|
/**
|
||||||
accumulator += mCoefficients[ mCenterCoefficient ] *
|
* Inserts the sample into the buffer without calculating a filtered value
|
||||||
mBuffer[ mIndexMap[ mBufferPointer ][ mCenterCoefficientMapIndex ] ];
|
*/
|
||||||
|
public void insert(float sample)
|
||||||
|
{
|
||||||
|
mBuffer[mBufferPointer] = sample;
|
||||||
|
|
||||||
//For the remaining coefficients, add the symmetric samples, oldest and newest
|
mBufferPointer++;
|
||||||
//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;
|
mBufferPointer = mBufferPointer % mBufferSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public ReusableBuffer filter(ReusableBuffer input)
|
||||||
* Creates an n X (n + 1 / 2) index map enabling quick access to the
|
{
|
||||||
* circular buffer samples.
|
float[] inputSamples = input.getSamples();
|
||||||
*
|
ReusableBuffer output = mReusableBufferQueue.getBuffer(inputSamples.length);
|
||||||
* As the buffer shifts right with each subsequent sample, we have to move
|
float[] outputSamples = output.getSamples();
|
||||||
* 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;
|
for(int x = 0; x < inputSamples.length; x++)
|
||||||
|
{
|
||||||
|
outputSamples[x] = filter(inputSamples[x]);
|
||||||
|
}
|
||||||
|
|
||||||
//Set center tap index for coefficients array
|
output.incrementUserCount();
|
||||||
mCenterCoefficient = ( size - 1 ) / 2;
|
input.decrementUserCount();
|
||||||
mCenterCoefficientMapIndex = mCenterCoefficient + 1;
|
|
||||||
|
|
||||||
mIndexMap = new int[ size ][ mapWidth ];
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
//Setup the first row, buffer pointer index 0, as a starting point
|
/**
|
||||||
for( int x = 0; x < mapWidth - 2; x += 2 )
|
* Calculate the filtered value by applying the coefficients against the samples in mBuffer
|
||||||
{
|
*/
|
||||||
mIndexMap[ 0 ][ x ] = x;
|
public float filter(float sample)
|
||||||
mIndexMap[ 0 ][ x + 1 ] = size - 1 - x;
|
{
|
||||||
}
|
insert(sample);
|
||||||
|
|
||||||
//Place center tap index in last element
|
float accumulator = 0;
|
||||||
mIndexMap[ 0 ][ mCenterCoefficientMapIndex ] = mCenterCoefficient;
|
|
||||||
|
|
||||||
//For each subsequent row, increment the previous row's value by 1,
|
//Start with the center tap value
|
||||||
//subtracting size as needed, to keep the values between 0 and size - 1
|
accumulator += mCoefficients[mCenterCoefficient] * mBuffer[mIndexMap[mBufferPointer][mCenterCoefficientMapIndex]];
|
||||||
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 )
|
//For the remaining coefficients, add the symmetric samples, oldest and newest
|
||||||
{
|
//first, then multiply by the single coefficient
|
||||||
mIndexMap[ x ][ y ] -= size;
|
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;
|
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.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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.Arrays;
|
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 float[] mHilbertFilter;
|
||||||
|
private float[] mBuffer;
|
||||||
private Listener<ComplexBuffer> mListener;
|
|
||||||
|
|
||||||
private float[] mBuffer;
|
|
||||||
private int mBufferSize;
|
private int mBufferSize;
|
||||||
private int mBufferPointer;
|
private int mBufferPointer;
|
||||||
|
private int[][] mIndexMap;
|
||||||
|
private int mMapHeight;
|
||||||
|
private int mCenterTapIndex;
|
||||||
|
private float mCenterCoefficent;
|
||||||
|
|
||||||
private int[][] mIndexMap;
|
private boolean mInvertFlag = false;
|
||||||
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;
|
||||||
* Hilbert transform filter used for converting real-valued samples into
|
mBuffer = new float[mBufferSize];
|
||||||
* 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;
|
mCenterCoefficent = mHilbertFilter[mHilbertFilter.length / 2];
|
||||||
mBuffer = new float[ mBufferSize ];
|
|
||||||
|
|
||||||
mCenterCoefficent = mHilbertFilter[ mHilbertFilter.length / 2 ];
|
generateIndexMap(mHilbertFilter.length);
|
||||||
|
}
|
||||||
|
|
||||||
generateIndexMap( mHilbertFilter.length );
|
/**
|
||||||
}
|
* 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;
|
||||||
|
|
||||||
@Override
|
for(int y = 0; y < samples.length; y += 2)
|
||||||
public void receive( RealBuffer buffer )
|
{
|
||||||
{
|
insert(samples[y]);
|
||||||
if( mListener != null )
|
insert(samples[y + 1]);
|
||||||
{
|
|
||||||
mListener.receive( filter( buffer ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
accumulator = 0.0f;
|
||||||
* Sets the listener to receive filtered complex buffers.
|
|
||||||
*/
|
|
||||||
public void setListener( Listener<ComplexBuffer> listener )
|
|
||||||
{
|
|
||||||
mListener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
int index = mBufferPointer / 2;
|
||||||
* 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() ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
for(int x = 0; x < mHilbertFilter.length / 2; x += 2)
|
||||||
* Filters the continuous real-sample stream by treating the stream as a
|
{
|
||||||
* set of interleaved values and applying two hilbert filters against each
|
accumulator += mHilbertFilter[x] *
|
||||||
* interleaved set (I & Q). Frequency translation has already been
|
(mBuffer[mIndexMap[index][x + 1]] -
|
||||||
* pre-applied to the hilbert filter coefficients.
|
mBuffer[mIndexMap[index][x]]);
|
||||||
*
|
}
|
||||||
* Filtering of the inphase component is a simple multiply by the center
|
|
||||||
* coefficient (0.5) since all of the other coefficients are zero-valued.
|
|
||||||
*
|
|
||||||
* Filtering of the quadrature component uses a folded FIR structure and
|
|
||||||
* expects the hilbert coefficients to be symmetric with all negative valued
|
|
||||||
* coefficients below the center coefficient and all positive-valued coefficients
|
|
||||||
* above the center coefficient. So, convolution only applies the filter
|
|
||||||
* coefficient against the difference of the two samples.
|
|
||||||
*
|
|
||||||
* ( coefficient * value1 ) + (-coefficient * value2 ) = coefficient * ( value1 - value2 )
|
|
||||||
*
|
|
||||||
* Performs frequency translation by FS/2 on the final filtered values by
|
|
||||||
* applying a sequence of 1,-1 (sign change) to each I/Q output sample.
|
|
||||||
*/
|
|
||||||
public float[] filter( float[] samples )
|
|
||||||
{
|
|
||||||
float accumulator;
|
|
||||||
|
|
||||||
for( int y = 0; y < samples.length; y +=2 )
|
//Perform FS/2 frequency translation on the final filtered values
|
||||||
{
|
if(mInvertFlag)
|
||||||
insert( samples[ y ] );
|
{
|
||||||
insert( samples[ y + 1 ] );
|
//inphase
|
||||||
|
samples[y] = -(mBuffer[mIndexMap[index][mCenterTapIndex]]);
|
||||||
|
|
||||||
accumulator = 0.0f;
|
//quadrature
|
||||||
|
samples[y + 1] = -accumulator;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//inphase
|
||||||
|
samples[y] = mBuffer[mIndexMap[index][mCenterTapIndex]];
|
||||||
|
|
||||||
int index = mBufferPointer / 2;
|
//quadrature
|
||||||
|
samples[y + 1] = accumulator;
|
||||||
|
}
|
||||||
|
|
||||||
for( int x = 0; x < mHilbertFilter.length / 2; x += 2 )
|
mInvertFlag = !mInvertFlag;
|
||||||
{
|
}
|
||||||
accumulator += mHilbertFilter[ x ] *
|
|
||||||
( mBuffer[ mIndexMap[ index ][ x + 1] ] -
|
|
||||||
mBuffer[ mIndexMap[ index ][ x ] ] );
|
|
||||||
}
|
|
||||||
|
|
||||||
//Perform FS/2 frequency translation on the final filtered values
|
return samples;
|
||||||
if( mInvertFlag )
|
}
|
||||||
{
|
|
||||||
//inphase
|
|
||||||
samples[ y ] = -( mBuffer[ mIndexMap[ index ][ mCenterTapIndex ] ] );
|
|
||||||
|
|
||||||
//quadrature
|
/**
|
||||||
samples[ y + 1 ] = -accumulator;
|
* Inserts the sample into the circular buffer, overwriting the oldest value
|
||||||
}
|
*/
|
||||||
else
|
private void insert(float sample)
|
||||||
{
|
{
|
||||||
//inphase
|
mBuffer[mBufferPointer++] = sample;
|
||||||
samples[ y ] = mBuffer[ mIndexMap[ index ][ mCenterTapIndex ] ];
|
|
||||||
|
|
||||||
//quadrature
|
mBufferPointer = mBufferPointer % mBufferSize;
|
||||||
samples[ y + 1 ] = accumulator;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
mInvertFlag = !mInvertFlag;
|
/**
|
||||||
}
|
* 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;
|
||||||
|
|
||||||
return samples;
|
mIndexMap = new int[mMapHeight][mapWidth];
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
//Setup the first row with buffer pointer index 0 as a starting point
|
||||||
* Inserts the sample into the circular buffer, overwriting the oldest value
|
for(int x = 0; x < mapWidth - 1; x += 2)
|
||||||
*/
|
{
|
||||||
private void insert( float sample )
|
mIndexMap[0][x] = size - 1 - x;
|
||||||
{
|
mIndexMap[0][x + 1] = x;
|
||||||
mBuffer[ mBufferPointer++ ] = sample;
|
}
|
||||||
|
|
||||||
mBufferPointer = mBufferPointer % mBufferSize;
|
//Place the center index at the end of the array
|
||||||
}
|
mCenterTapIndex = mapWidth - 1;
|
||||||
|
|
||||||
/**
|
mIndexMap[0][mCenterTapIndex] = size / 2;
|
||||||
* 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 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;
|
||||||
|
|
||||||
//Setup the first row with buffer pointer index 0 as a starting point
|
if(mIndexMap[x][y] >= size)
|
||||||
for( int x = 0; x < mapWidth - 1; x += 2 )
|
{
|
||||||
{
|
mIndexMap[x][y] -= size + 1;
|
||||||
mIndexMap[ 0 ][ x ] = size - 1 - x;
|
|
||||||
mIndexMap[ 0 ][ x + 1 ] = x;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Place the center index at the end of the array
|
//Special handling for center tap wrap around
|
||||||
mCenterTapIndex = mapWidth - 1;
|
if(y == mCenterTapIndex && mIndexMap[x][y] < 0)
|
||||||
|
{
|
||||||
|
mIndexMap[x][y] = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mIndexMap[ 0 ][ mCenterTapIndex ] = size / 2;
|
/**
|
||||||
|
* Logs the index map contents
|
||||||
|
*/
|
||||||
|
public void logIndexMap()
|
||||||
|
{
|
||||||
|
for(int[] indexSet : mIndexMap)
|
||||||
|
{
|
||||||
|
mLog.debug("Row:" + Arrays.toString(indexSet));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//For each subsequent row, increment the previous row's value by 2,
|
/**
|
||||||
//wrapping as needed, to keep the values between 0 and size - 1
|
* Converts the half-band filter coefficients for use as hilbert transform
|
||||||
for( int x = 1; x < mMapHeight; x++ )
|
* filter coefficients. Sets all even-numbered coefficients left of center
|
||||||
{
|
* to negative and all even-numbered coefficients right of center to positive
|
||||||
for( int y = 0; y < mapWidth; y++ )
|
* which applies a FS/4 frequency translation to the coefficients.
|
||||||
{
|
*
|
||||||
mIndexMap[ x ][ y ] = mIndexMap[ x - 1 ][ y ] + 2;
|
* 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];
|
||||||
|
|
||||||
if( mIndexMap[ x ][ y ] >= size )
|
int middle = coefficients.length / 2;
|
||||||
{
|
|
||||||
mIndexMap[ x ][ y ] -= size + 1;
|
|
||||||
|
|
||||||
//Special handling for center tap wrap around
|
for(int x = 0; x < coefficients.length; x++)
|
||||||
if( y == mCenterTapIndex && mIndexMap[ x ][ y ] < 0 )
|
{
|
||||||
{
|
if(x < middle)
|
||||||
mIndexMap[ x ][ y ] = size;
|
{
|
||||||
}
|
mHilbertFilter[x] = 2.0f * -Math.abs(coefficients[x]);
|
||||||
}
|
}
|
||||||
}
|
else if(x > middle)
|
||||||
}
|
{
|
||||||
}
|
mHilbertFilter[x] = 2.0f * Math.abs(coefficients[x]);
|
||||||
|
}
|
||||||
/**
|
else
|
||||||
* Logs the index map contents
|
{
|
||||||
*/
|
mHilbertFilter[x] = 2.0f * coefficients[x];
|
||||||
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;
|
package io.github.dsheirer.dsp.filter.iir;
|
||||||
|
|
||||||
import io.github.dsheirer.sample.real.RealBuffer;
|
|
||||||
|
|
||||||
public class DeemphasisFilter
|
public class DeemphasisFilter
|
||||||
{
|
{
|
||||||
private static final float MAX_SAMPLE_VALUE = 0.95f;
|
private static final float MAX_SAMPLE_VALUE = 0.95f;
|
||||||
@@ -45,16 +58,4 @@ public class DeemphasisFilter
|
|||||||
|
|
||||||
return samples;
|
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.ReusableComplexBuffer;
|
||||||
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
|
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
|
||||||
import io.github.dsheirer.sample.complex.Complex;
|
import io.github.dsheirer.sample.complex.Complex;
|
||||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
|
||||||
import io.github.dsheirer.sample.complex.ComplexSampleListener;
|
import io.github.dsheirer.sample.complex.ComplexSampleListener;
|
||||||
|
|
||||||
public class ComplexFeedForwardGainControl implements ComplexSampleListener
|
public class ComplexFeedForwardGainControl implements ComplexSampleListener
|
||||||
@@ -137,17 +136,6 @@ public class ComplexFeedForwardGainControl implements ComplexSampleListener
|
|||||||
return processed;
|
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
|
* Applies gain to the complex sample buffer
|
||||||
* @param buffer to apply gain
|
* @param buffer to apply gain
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package io.github.dsheirer.dsp.gain;
|
package io.github.dsheirer.dsp.gain;
|
||||||
|
|
||||||
import io.github.dsheirer.sample.real.RealBuffer;
|
|
||||||
|
|
||||||
public class NonClippingGain
|
public class NonClippingGain
|
||||||
{
|
{
|
||||||
private float mGain;
|
private float mGain;
|
||||||
@@ -50,16 +48,4 @@ public class NonClippingGain
|
|||||||
|
|
||||||
return samples;
|
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.FS4DownConverter;
|
||||||
import io.github.dsheirer.dsp.mixer.IOscillator;
|
import io.github.dsheirer.dsp.mixer.IOscillator;
|
||||||
import io.github.dsheirer.dsp.mixer.LowPhaseNoiseOscillator;
|
import io.github.dsheirer.dsp.mixer.LowPhaseNoiseOscillator;
|
||||||
|
import io.github.dsheirer.sample.IOverflowListener;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.sample.SampleType;
|
import io.github.dsheirer.sample.SampleType;
|
||||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||||
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
|
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
|
||||||
import io.github.dsheirer.sample.real.IOverflowListener;
|
|
||||||
import io.github.dsheirer.settings.SettingsManager;
|
import io.github.dsheirer.settings.SettingsManager;
|
||||||
import io.github.dsheirer.source.SourceEvent;
|
import io.github.dsheirer.source.SourceEvent;
|
||||||
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;
|
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.ReusableBuffer;
|
||||||
import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster;
|
import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster;
|
||||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
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.ComplexSource;
|
||||||
import io.github.dsheirer.source.ISourceEventListener;
|
import io.github.dsheirer.source.ISourceEventListener;
|
||||||
import io.github.dsheirer.source.ISourceEventProvider;
|
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 final static Logger mLog = LoggerFactory.getLogger(ProcessingChain.class);
|
||||||
|
|
||||||
private Broadcaster<AttributeChangeRequest> mAttributeChangeRequestBroadcaster = new Broadcaster<>();
|
|
||||||
private ReusableBufferBroadcaster<ReusableAudioPacket> mAudioPacketBroadcaster = new ReusableBufferBroadcaster<>();
|
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<CallEvent> mCallEventBroadcaster = new Broadcaster<>();
|
||||||
private Broadcaster<ChannelEvent> mChannelEventBroadcaster = 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<DecoderStateEvent> mDecoderStateEventBroadcaster = new Broadcaster<>();
|
||||||
private Broadcaster<Heartbeat> mHeartbeatBroadcaster = new Broadcaster<>();
|
private Broadcaster<Heartbeat> mHeartbeatBroadcaster = new Broadcaster<>();
|
||||||
private Broadcaster<SourceEvent> mSourceEventBroadcaster = new Broadcaster<>();
|
private Broadcaster<SourceEvent> mSourceEventBroadcaster = new Broadcaster<>();
|
||||||
private Broadcaster<Message> mMessageBroadcaster = 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 Broadcaster<SquelchState> mSquelchStateBroadcaster = new Broadcaster<>();
|
||||||
|
|
||||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||||
@@ -169,9 +162,8 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
mAudioPacketBroadcaster.dispose();
|
mAudioPacketBroadcaster.dispose();
|
||||||
mCallEventBroadcaster.dispose();
|
mCallEventBroadcaster.dispose();
|
||||||
mChannelEventBroadcaster.dispose();
|
mChannelEventBroadcaster.dispose();
|
||||||
mComplexBufferBroadcaster.dispose();
|
mBasebandComplexBufferBroadcaster.dispose();
|
||||||
mMessageBroadcaster.dispose();
|
mMessageBroadcaster.dispose();
|
||||||
mFilteredRealBufferBroadcaster.dispose();
|
|
||||||
mSquelchStateBroadcaster.dispose();
|
mSquelchStateBroadcaster.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,11 +308,6 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
mDecoderStateEventBroadcaster.addListener(((IDecoderStateEventListener) module).getDecoderStateListener());
|
mDecoderStateEventBroadcaster.addListener(((IDecoderStateEventListener) module).getDecoderStateListener());
|
||||||
}
|
}
|
||||||
|
|
||||||
if(module instanceof IFilteredRealBufferListener)
|
|
||||||
{
|
|
||||||
mFilteredRealBufferBroadcaster.addListener(((IFilteredRealBufferListener) module).getFilteredRealBufferListener());
|
|
||||||
}
|
|
||||||
|
|
||||||
if(module instanceof IHeartbeatListener)
|
if(module instanceof IHeartbeatListener)
|
||||||
{
|
{
|
||||||
mHeartbeatBroadcaster.addListener(((IHeartbeatListener)module).getHeartbeatListener());
|
mHeartbeatBroadcaster.addListener(((IHeartbeatListener)module).getHeartbeatListener());
|
||||||
@@ -338,7 +325,7 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
|
|
||||||
if(module instanceof IReusableComplexBufferListener)
|
if(module instanceof IReusableComplexBufferListener)
|
||||||
{
|
{
|
||||||
mComplexBufferBroadcaster.addListener(((IReusableComplexBufferListener)module).getReusableComplexBufferListener());
|
mBasebandComplexBufferBroadcaster.addListener(((IReusableComplexBufferListener)module).getReusableComplexBufferListener());
|
||||||
}
|
}
|
||||||
|
|
||||||
if(module instanceof ISourceEventListener)
|
if(module instanceof ISourceEventListener)
|
||||||
@@ -355,11 +342,6 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
{
|
{
|
||||||
mSquelchStateBroadcaster.addListener(((ISquelchStateListener) module).getSquelchStateListener());
|
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());
|
mDecoderStateEventBroadcaster.removeListener(((IDecoderStateEventListener) module).getDecoderStateListener());
|
||||||
}
|
}
|
||||||
|
|
||||||
if(module instanceof IFilteredRealBufferListener)
|
|
||||||
{
|
|
||||||
mFilteredRealBufferBroadcaster.removeListener(((IFilteredRealBufferListener) module).getFilteredRealBufferListener());
|
|
||||||
}
|
|
||||||
|
|
||||||
if(module instanceof IHeartbeatListener)
|
if(module instanceof IHeartbeatListener)
|
||||||
{
|
{
|
||||||
mHeartbeatBroadcaster.removeListener(((IHeartbeatListener)module).getHeartbeatListener());
|
mHeartbeatBroadcaster.removeListener(((IHeartbeatListener)module).getHeartbeatListener());
|
||||||
@@ -410,7 +387,7 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
|
|
||||||
if(module instanceof IReusableComplexBufferListener)
|
if(module instanceof IReusableComplexBufferListener)
|
||||||
{
|
{
|
||||||
mComplexBufferBroadcaster.removeListener(((IReusableComplexBufferListener) module).getReusableComplexBufferListener());
|
mBasebandComplexBufferBroadcaster.removeListener(((IReusableComplexBufferListener) module).getReusableComplexBufferListener());
|
||||||
}
|
}
|
||||||
|
|
||||||
if(module instanceof ISourceEventListener)
|
if(module instanceof ISourceEventListener)
|
||||||
@@ -427,11 +404,6 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
{
|
{
|
||||||
mSquelchStateBroadcaster.removeListener(((ISquelchStateListener) module).getSquelchStateListener());
|
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);
|
((IMessageProvider) module).setMessageListener(mMessageBroadcaster);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(module instanceof IFilteredRealBufferProvider)
|
|
||||||
{
|
|
||||||
((IFilteredRealBufferProvider) module).setFilteredRealBufferListener(mFilteredRealBufferBroadcaster);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(module instanceof IMessageProvider)
|
if(module instanceof IMessageProvider)
|
||||||
{
|
{
|
||||||
((IMessageProvider) module).setMessageListener(mMessageBroadcaster);
|
((IMessageProvider) module).setMessageListener(mMessageBroadcaster);
|
||||||
@@ -499,11 +466,6 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
{
|
{
|
||||||
((ISquelchStateProvider) module).setSquelchStateListener(mSquelchStateBroadcaster);
|
((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);
|
((IMessageProvider) module).setMessageListener(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(module instanceof IFilteredRealBufferProvider)
|
|
||||||
{
|
|
||||||
((IFilteredRealBufferProvider) module).setFilteredRealBufferListener(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(module instanceof IMessageProvider)
|
if(module instanceof IMessageProvider)
|
||||||
{
|
{
|
||||||
((IMessageProvider) module).setMessageListener(null);
|
((IMessageProvider) module).setMessageListener(null);
|
||||||
@@ -571,11 +528,6 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
{
|
{
|
||||||
((ISquelchStateProvider) module).setSquelchStateListener(null);
|
((ISquelchStateProvider) module).setSquelchStateListener(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(module instanceof IUnFilteredRealBufferProvider)
|
|
||||||
{
|
|
||||||
((IUnFilteredRealBufferProvider) module).setUnFilteredRealBufferListener(null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -609,7 +561,7 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
switch(mSource.getSampleType())
|
switch(mSource.getSampleType())
|
||||||
{
|
{
|
||||||
case COMPLEX:
|
case COMPLEX:
|
||||||
((ComplexSource) mSource).setListener(mComplexBufferBroadcaster);
|
((ComplexSource) mSource).setListener(mBasebandComplexBufferBroadcaster);
|
||||||
break;
|
break;
|
||||||
case REAL:
|
case REAL:
|
||||||
((RealSource) mSource).setListener(mDemodulatedAudioBufferBroadcaster);
|
((RealSource) mSource).setListener(mDemodulatedAudioBufferBroadcaster);
|
||||||
@@ -657,7 +609,7 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
switch(mSource.getSampleType())
|
switch(mSource.getSampleType())
|
||||||
{
|
{
|
||||||
case COMPLEX:
|
case COMPLEX:
|
||||||
((ComplexSource) mSource).removeListener(mComplexBufferBroadcaster);
|
((ComplexSource) mSource).removeListener(mBasebandComplexBufferBroadcaster);
|
||||||
break;
|
break;
|
||||||
case REAL:
|
case REAL:
|
||||||
((RealSource) mSource).removeListener(mDemodulatedAudioBufferBroadcaster);
|
((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)
|
public void removeMessageListener(Listener<Message> listener)
|
||||||
{
|
{
|
||||||
mMessageBroadcaster.removeListener(listener);
|
mMessageBroadcaster.removeListener(listener);
|
||||||
@@ -832,14 +787,20 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
mSquelchStateBroadcaster.removeListener(listener);
|
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
|
@Override
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ package io.github.dsheirer.module.decode.p25;
|
|||||||
|
|
||||||
import io.github.dsheirer.dsp.gain.DirectGainControl;
|
import io.github.dsheirer.dsp.gain.DirectGainControl;
|
||||||
import io.github.dsheirer.sample.Listener;
|
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.RealSampleListener;
|
||||||
import io.github.dsheirer.sample.real.RealSampleProvider;
|
import io.github.dsheirer.sample.real.RealSampleProvider;
|
||||||
import io.github.dsheirer.source.ISourceEventListener;
|
import io.github.dsheirer.source.ISourceEventListener;
|
||||||
import io.github.dsheirer.source.SourceEvent;
|
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[][] =
|
private static final float TAPS[][] =
|
||||||
{
|
{
|
||||||
@@ -215,7 +215,7 @@ public class C4FMSymbolFilter implements Listener<RealBuffer>, ISourceEventListe
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void receive(RealBuffer buffer)
|
public void receive(ReusableBuffer buffer)
|
||||||
{
|
{
|
||||||
for(float sample : buffer.getSamples())
|
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;
|
package io.github.dsheirer.module.decode.p25.audio;
|
||||||
|
|
||||||
import io.github.dsheirer.audio.AudioFormats;
|
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.properties.SystemProperties;
|
||||||
import io.github.dsheirer.record.wave.AudioPacketWaveRecorder;
|
import io.github.dsheirer.record.wave.AudioPacketWaveRecorder;
|
||||||
import io.github.dsheirer.record.wave.ComplexBufferWaveRecorder;
|
import io.github.dsheirer.record.wave.ComplexBufferWaveRecorder;
|
||||||
|
import io.github.dsheirer.sample.IOverflowListener;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.sample.OverflowableTransferQueue;
|
import io.github.dsheirer.sample.OverflowableTransferQueue;
|
||||||
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
|
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
|
||||||
import io.github.dsheirer.sample.real.IOverflowListener;
|
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package io.github.dsheirer.sample;
|
package io.github.dsheirer.sample;
|
||||||
|
|
||||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||||
import io.github.dsheirer.sample.real.RealBuffer;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
@@ -53,24 +52,6 @@ public class ConversionUtils
|
|||||||
return converted;
|
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
|
* Converts the float samples in a complex buffer to a little endian 16-bit
|
||||||
* buffer
|
* buffer
|
||||||
|
|||||||
@@ -13,17 +13,13 @@
|
|||||||
* If not, see <http://www.gnu.org/licenses/>
|
* 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 interface IOverflowListener
|
||||||
|
|
||||||
public class ComplexBuffer extends Buffer
|
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* 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)
|
void sourceOverflow(boolean overflow);
|
||||||
{
|
|
||||||
super(samples);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,6 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package io.github.dsheirer.sample;
|
package io.github.dsheirer.sample;
|
||||||
|
|
||||||
import io.github.dsheirer.sample.real.IOverflowListener;
|
|
||||||
import io.github.dsheirer.source.Source;
|
import io.github.dsheirer.source.Source;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|||||||
@@ -15,131 +15,66 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package io.github.dsheirer.sample.buffer;
|
package io.github.dsheirer.sample.buffer;
|
||||||
|
|
||||||
import io.github.dsheirer.dsp.filter.channelizer.SampleTimestampManager;
|
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import org.apache.commons.lang3.Validate;
|
import io.github.dsheirer.sample.real.RealSampleListener;
|
||||||
|
|
||||||
import java.nio.FloatBuffer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assembles complex float samples into a ComplexBuffer containing an array of floats. Monitors incoming sample count
|
* Assembles single float samples into a ReusableBuffer containing an array of floats
|
||||||
* to accurately assign a relative timestamp to each assembled buffer.
|
|
||||||
*/
|
*/
|
||||||
public class ReusableBufferAssembler
|
public class ReusableBufferAssembler implements RealSampleListener
|
||||||
{
|
{
|
||||||
private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("ReusableBufferAssembler");
|
|
||||||
private SampleTimestampManager mTimestampManager;
|
|
||||||
private FloatBuffer mBuffer;
|
|
||||||
private int mBufferSize;
|
private int mBufferSize;
|
||||||
private long mCurrentBufferTimestamp;
|
private int mBufferPointer;
|
||||||
private Listener<ReusableComplexBuffer> mListener;
|
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;
|
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()
|
public void dispose()
|
||||||
{
|
{
|
||||||
mReusableComplexBufferQueue.dispose();
|
|
||||||
mListener = null;
|
mListener = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void reset()
|
||||||
* 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)
|
mCurrentBuffer.decrementUserCount();
|
||||||
{
|
mCurrentBuffer = null;
|
||||||
mBuffer.put(samples);
|
mBufferPointer = 0;
|
||||||
mTimestampManager.increment(samples.length / 2);
|
}
|
||||||
|
|
||||||
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
|
||||||
else
|
|
||||||
{
|
|
||||||
int offset = 0;
|
|
||||||
|
|
||||||
while(offset < samples.length)
|
|
||||||
{
|
{
|
||||||
int toCopy = mBuffer.remaining();
|
mCurrentBuffer.decrementUserCount();
|
||||||
|
|
||||||
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 = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void setListener(Listener<ReusableBuffer> listener)
|
||||||
* 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;
|
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;
|
package io.github.dsheirer.source;
|
||||||
|
|
||||||
import io.github.dsheirer.module.Module;
|
import io.github.dsheirer.module.Module;
|
||||||
|
import io.github.dsheirer.sample.IOverflowListener;
|
||||||
import io.github.dsheirer.sample.SampleType;
|
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.HeartbeatManager;
|
||||||
import io.github.dsheirer.source.heartbeat.IHeartbeatProvider;
|
import io.github.dsheirer.source.heartbeat.IHeartbeatProvider;
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public class ComplexMixer
|
|||||||
* @param targetDataLine - mixer or sound card to be used
|
* @param targetDataLine - mixer or sound card to be used
|
||||||
* @param format - audio format
|
* @param format - audio format
|
||||||
* @param name - token name to use for this mixer
|
* @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,
|
public ComplexMixer(TargetDataLine targetDataLine, AudioFormat format, String name,
|
||||||
ComplexSampleAdapter complexSampleAdapter)
|
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.filter.cic.ComplexPrimeCICDecimate;
|
||||||
import io.github.dsheirer.dsp.mixer.IOscillator;
|
import io.github.dsheirer.dsp.mixer.IOscillator;
|
||||||
import io.github.dsheirer.dsp.mixer.Oscillator;
|
import io.github.dsheirer.dsp.mixer.Oscillator;
|
||||||
import io.github.dsheirer.sample.Buffer;
|
|
||||||
import io.github.dsheirer.sample.Listener;
|
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.Complex;
|
||||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
|
||||||
import io.github.dsheirer.source.tuner.Tuner;
|
import io.github.dsheirer.source.tuner.Tuner;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -45,12 +45,12 @@ public class CICTunerChannelSource
|
|||||||
private static double CHANNEL_RATE = 48000.0;
|
private static double CHANNEL_RATE = 48000.0;
|
||||||
private static int CHANNEL_PASS_FREQUENCY = 12000;
|
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 IOscillator mFrequencyCorrectionMixer;
|
||||||
private ComplexPrimeCICDecimate mDecimationFilter;
|
private ComplexPrimeCICDecimate mDecimationFilter;
|
||||||
private Listener<ComplexBuffer> mListener;
|
private Listener<ReusableComplexBuffer> mListener;
|
||||||
private List<ComplexBuffer> mSampleBuffers = new ArrayList<ComplexBuffer>();
|
private List<ReusableComplexBuffer> mSampleBuffers = new ArrayList<>();
|
||||||
private long mTunerFrequency = 0;
|
private long mTunerFrequency = 0;
|
||||||
private double mTunerSampleRate;
|
private double mTunerSampleRate;
|
||||||
private long mChannelFrequencyCorrection = 0;
|
private long mChannelFrequencyCorrection = 0;
|
||||||
@@ -68,7 +68,7 @@ public class CICTunerChannelSource
|
|||||||
*/
|
*/
|
||||||
public CICTunerChannelSource(Tuner tuner, TunerChannel tunerChannel)
|
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);
|
// mBuffer.setSourceOverflowListener(this);
|
||||||
|
|
||||||
//Setup the frequency translator to the current source frequency
|
//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)
|
* Primary input method for receiving complex sample buffers from the wideband source (ie tuner)
|
||||||
* @param buffer to receive and eventually process
|
* @param buffer to receive and eventually process
|
||||||
*/
|
*/
|
||||||
public void receive(ComplexBuffer buffer)
|
public void receive(ReusableComplexBuffer buffer)
|
||||||
{
|
{
|
||||||
mBuffer.offer(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
|
/* Save a pointer to the listener so that if we have to change the
|
||||||
* decimation filter, we can re-add the listener */
|
* decimation filter, we can re-add the listener */
|
||||||
@@ -134,7 +134,7 @@ public class CICTunerChannelSource
|
|||||||
mDecimationFilter.setListener(listener);
|
mDecimationFilter.setListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeListener(Listener<ComplexBuffer> listener)
|
public void removeListener(Listener<ReusableComplexBuffer> listener)
|
||||||
{
|
{
|
||||||
mDecimationFilter.removeListener();
|
mDecimationFilter.removeListener();
|
||||||
}
|
}
|
||||||
@@ -181,14 +181,13 @@ public class CICTunerChannelSource
|
|||||||
{
|
{
|
||||||
mBuffer.drainTo(mSampleBuffers, 20);
|
mBuffer.drainTo(mSampleBuffers, 20);
|
||||||
|
|
||||||
for(Buffer buffer : mSampleBuffers)
|
for(ReusableComplexBuffer buffer : mSampleBuffers)
|
||||||
{
|
{
|
||||||
float[] samples = buffer.getSamples();
|
float[] samples = buffer.getSamples();
|
||||||
|
|
||||||
/* We make a copy of the buffer so that we don't affect
|
ReusableComplexBuffer reusableComplexBuffer = mReusableComplexBufferQueue.getBuffer(samples.length);
|
||||||
* anyone else that is using the same buffer, like other
|
reusableComplexBuffer.incrementUserCount();
|
||||||
* channels or the spectral display */
|
float[] translated = reusableComplexBuffer.getSamples();
|
||||||
float[] translated = new float[samples.length];
|
|
||||||
|
|
||||||
/* Perform frequency translation */
|
/* Perform frequency translation */
|
||||||
for(int x = 0; x < samples.length; x += 2)
|
for(int x = 0; x < samples.length; x += 2)
|
||||||
@@ -206,8 +205,10 @@ public class CICTunerChannelSource
|
|||||||
|
|
||||||
if(filter != null)
|
if(filter != null)
|
||||||
{
|
{
|
||||||
filter.receive(new ComplexBuffer(translated));
|
filter.receive(reusableComplexBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buffer.decrementUserCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
mSampleBuffers.clear();
|
mSampleBuffers.clear();
|
||||||
|
|||||||
@@ -21,12 +21,13 @@ import io.github.dsheirer.controller.channel.ChannelEventListener;
|
|||||||
import io.github.dsheirer.controller.channel.ChannelProcessingManager;
|
import io.github.dsheirer.controller.channel.ChannelProcessingManager;
|
||||||
import io.github.dsheirer.dsp.filter.Filters;
|
import io.github.dsheirer.dsp.filter.Filters;
|
||||||
import io.github.dsheirer.dsp.filter.Window.WindowType;
|
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.dsp.filter.smoothing.SmoothingFilter.SmoothingType;
|
||||||
import io.github.dsheirer.module.ProcessingChain;
|
import io.github.dsheirer.module.ProcessingChain;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.sample.SampleType;
|
import io.github.dsheirer.sample.SampleType;
|
||||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
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.sample.buffer.ReusableComplexBufferQueue;
|
||||||
import io.github.dsheirer.settings.ColorSetting.ColorSettingName;
|
import io.github.dsheirer.settings.ColorSetting.ColorSettingName;
|
||||||
import io.github.dsheirer.settings.ColorSettingMenuItem;
|
import io.github.dsheirer.settings.ColorSettingMenuItem;
|
||||||
@@ -74,7 +75,7 @@ public class ChannelSpectrumPanel extends JPanel implements ChannelEventListener
|
|||||||
|
|
||||||
private int mSampleBufferSize = 2400;
|
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);
|
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 1.0f, true);
|
||||||
|
|
||||||
private AtomicBoolean mEnabled = new AtomicBoolean();
|
private AtomicBoolean mEnabled = new AtomicBoolean();
|
||||||
@@ -215,8 +216,7 @@ public class ChannelSpectrumPanel extends JPanel implements ChannelEventListener
|
|||||||
|
|
||||||
if(processingChain != null)
|
if(processingChain != null)
|
||||||
{
|
{
|
||||||
// processingChain.addRealBufferListener(this);
|
processingChain.addDemodulatedAudioListener(this);
|
||||||
|
|
||||||
mDFTProcessor.start();
|
mDFTProcessor.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -226,12 +226,11 @@ public class ChannelSpectrumPanel extends JPanel implements ChannelEventListener
|
|||||||
{
|
{
|
||||||
if(mCurrentChannel != null && mCurrentChannel.isProcessing())
|
if(mCurrentChannel != null && mCurrentChannel.isProcessing())
|
||||||
{
|
{
|
||||||
ProcessingChain processingChain = mChannelProcessingManager
|
ProcessingChain processingChain = mChannelProcessingManager.getProcessingChain(mCurrentChannel);
|
||||||
.getProcessingChain(mCurrentChannel);
|
|
||||||
|
|
||||||
if(processingChain != null)
|
if(processingChain != null)
|
||||||
{
|
{
|
||||||
// processingChain.removeRealBufferListener(this);
|
processingChain.removeDemodulatedAudioListener(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,15 +269,14 @@ public class ChannelSpectrumPanel extends JPanel implements ChannelEventListener
|
|||||||
@Override
|
@Override
|
||||||
public void receive(ReusableBuffer buffer)
|
public void receive(ReusableBuffer buffer)
|
||||||
{
|
{
|
||||||
throw new IllegalStateException("This must be updated to work with reusable buffers");
|
ReusableBuffer decimated = mDecimatingFilter.filter(buffer);
|
||||||
// RealBuffer decimated = mDecimatingFilter.filter(buffer);
|
|
||||||
//
|
//Hack: we're placing real samples in a complex buffer that the DFT
|
||||||
// //Hack: we're placing real samples in a complex buffer that the DFT
|
//processor is expecting.
|
||||||
// //processor is expecting.
|
ReusableComplexBuffer reusableComplexBuffer = mReusableComplexBufferQueue.getBuffer(decimated.getSamples().length);
|
||||||
// ReusableComplexBuffer reusableComplexBuffer = mReusableComplexBufferQueue.getBuffer(decimated.getSamples().length);
|
reusableComplexBuffer.reloadFrom(decimated.getSamples(), System.currentTimeMillis());
|
||||||
// reusableComplexBuffer.reloadFrom(decimated.getSamples(), System.currentTimeMillis());
|
reusableComplexBuffer.incrementUserCount();
|
||||||
// reusableComplexBuffer.incrementUserCount();
|
mDFTProcessor.receive(reusableComplexBuffer);
|
||||||
// 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;
|
||||||
import io.github.dsheirer.dsp.filter.Window.WindowType;
|
import io.github.dsheirer.dsp.filter.Window.WindowType;
|
||||||
import io.github.dsheirer.properties.SystemProperties;
|
import io.github.dsheirer.properties.SystemProperties;
|
||||||
|
import io.github.dsheirer.sample.IOverflowListener;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.sample.SampleType;
|
import io.github.dsheirer.sample.SampleType;
|
||||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
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.ISourceEventProcessor;
|
||||||
import io.github.dsheirer.source.SourceEvent;
|
import io.github.dsheirer.source.SourceEvent;
|
||||||
import io.github.dsheirer.spectrum.converter.DFTResultsConverter;
|
import io.github.dsheirer.spectrum.converter.DFTResultsConverter;
|
||||||
|
|||||||
Reference in New Issue
Block a user