#1351 Restores semi-accurate timestamps of sample buffers to ensure P25 & DMR decoded messages have accurate timestamps and MBE recording voice frames have accurate timestamps. (#1357)

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
Denny Sheirer
2022-12-17 06:39:35 -05:00
committed by GitHub
parent 047dec6da1
commit bc462b9652
79 changed files with 607 additions and 733 deletions

View File

@@ -46,6 +46,7 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
private double mChannelSampleRate;
private long mIndexCenterFrequency;
private long mChannelFrequencyCorrection;
private long mCurrentSamplesTimestamp;
private ReentrantLock mOutputProcessorLock = new ReentrantLock();
/**
@@ -106,6 +107,7 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
public void receive(ComplexSamples complexSamples)
{
mStreamHeartbeatProcessor.receive(complexSamples);
mCurrentSamplesTimestamp = complexSamples.timestamp();
}
/**
@@ -222,7 +224,7 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
{
if(mPolyphaseChannelOutputProcessor != null)
{
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsList);
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsList, mCurrentSamplesTimestamp);
}
}
finally

View File

@@ -156,7 +156,7 @@ public class TwoChannelSynthesizerM2
mTopBlockFlag = !mTopBlockFlag;
}
return new ComplexSamples(i, q);
return new ComplexSamples(i, q, channelBuffer1.timestamp());
}
/**

View File

@@ -33,6 +33,7 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
private Dispatcher<List<float[]>> mChannelResultsDispatcher;
protected Listener<ComplexSamples> mComplexSamplesListener;
private int mInputChannelCount;
private long mCurrentSampleTimestamp = System.currentTimeMillis();
/**
* Base class for polyphase channelizer output channel processing. Provides built-in frequency translation
@@ -49,6 +50,15 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
mChannelResultsDispatcher.setListener(floats -> process(floats));
}
/**
* Timestamp for the current series of samples.
* @return time in milliseconds to use with assembled complex sample buffers.
*/
protected long getCurrentSampleTimestamp()
{
return mCurrentSampleTimestamp;
}
@Override
public void start()
{
@@ -81,9 +91,10 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
}
@Override
public void receiveChannelResults(List<float[]> channelResultsList)
public void receiveChannelResults(List<float[]> channelResultsList, long timestamp)
{
mChannelResultsDispatcher.receive(channelResultsList);
mCurrentSampleTimestamp = timestamp;
}
/**

View File

@@ -36,8 +36,9 @@ public interface IPolyphaseChannelOutputProcessor
/**
* Receive and enqueue output results from the polyphase analysis channelizer
* @param channelResults to enqueue
* @param timestamp for the first channel results buffer
*/
void receiveChannelResults(List<float[]> channelResults);
void receiveChannelResults(List<float[]> channelResults, long timestamp);
/**
* Listener to receive assembled complex samples buffers

View File

@@ -65,7 +65,8 @@ public abstract class MixerAssembler
/**
* Access the fully assembled buffer when hasBuffer() indicates that a buffer is ready.
* @param timestamp to use for the assembled complex samples buffer
* @return true if a full buffer is assembled and ready.
*/
public abstract ComplexSamples getBuffer();
public abstract ComplexSamples getBuffer(long timestamp);
}

View File

@@ -61,9 +61,9 @@ public class OneChannelMixerAssembler extends MixerAssembler
* @return assembled buffer.
*/
@Override
public ComplexSamples getBuffer()
public ComplexSamples getBuffer(long timestamp)
{
ComplexSamples buffer = mSampleAssembler.getBufferAndReset();
ComplexSamples buffer = mSampleAssembler.getBufferAndReset(timestamp);
if(getMixer().hasFrequency())
{

View File

@@ -90,7 +90,7 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
if(mMixerAssembler.hasBuffer())
{
ComplexSamples buffer = mMixerAssembler.getBuffer();
ComplexSamples buffer = mMixerAssembler.getBuffer(getCurrentSampleTimestamp());
if(mComplexSamplesListener != null)
{

View File

@@ -76,9 +76,9 @@ public class SampleAssembler
/**
* Extracts the full buffer from this assembler and resets the assembler to accept more samples.
*/
public ComplexSamples getBufferAndReset()
public ComplexSamples getBufferAndReset(long timestamp)
{
ComplexSamples buffer = new ComplexSamples(mI, mQ);
ComplexSamples buffer = new ComplexSamples(mI, mQ, timestamp);
reset();
return buffer;
}

View File

@@ -76,13 +76,14 @@ public class TwoChannelMixerAssembler extends MixerAssembler
/**
* Access the assembled buffer. Frequency offset and gain are applied to the returned buffer.
* @param timestamp to use for the assembled buffer
* @return assembled buffer.
*/
@Override
public ComplexSamples getBuffer()
public ComplexSamples getBuffer(long timestamp)
{
ComplexSamples channel1 = mChannel1SampleAssembler.getBufferAndReset();
ComplexSamples channel2 = mChannel2SampleAssembler.getBufferAndReset();
ComplexSamples channel1 = mChannel1SampleAssembler.getBufferAndReset(timestamp);
ComplexSamples channel2 = mChannel2SampleAssembler.getBufferAndReset(timestamp);
//Synthesize two channels into one, reducing sample rate by 2 so it's same as single channel rate
ComplexSamples buffer = mTwoChannelSynthesizer.process(channel1, channel2);

View File

@@ -90,7 +90,7 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
* Extract the channel from the channel results array, apply frequency translation, and deliver the
* extracted frequency-corrected channel I/Q sample set to the complex sample listener.
*
* @param lists to process containing a list of a list of an array of channel I/Q sample pairs (I0,Q0,I1,Q1...In,Qn)
* @param channelResultsList to process containing a list of a list of an array of channel I/Q sample pairs (I0,Q0,I1,Q1...In,Qn)
*/
@Override
public void process(List<float[]> channelResultsList)
@@ -102,7 +102,7 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
if(mMixerAssembler.hasBuffer())
{
ComplexSamples buffer = mMixerAssembler.getBuffer();
ComplexSamples buffer = mMixerAssembler.getBuffer(getCurrentSampleTimestamp());
if(mComplexSamplesListener != null)
{

View File

@@ -80,9 +80,10 @@ public abstract class HilbertTransform
/**
* Converts the real sample array to complex samples as half the sample rate
* @param samples to convert
* @param timestamp of the first sample
* @return converted samples
*/
public abstract ComplexSamples filter(float[] samples);
public abstract ComplexSamples filter(float[] samples, long timestamp);
/**
* Converts the half-band filter coefficients for use as hilbert transform filter coefficients. Sets all

View File

@@ -1,248 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
public class LegacyHilbertTransform
{
private static final Logger mLog = LoggerFactory.getLogger(LegacyHilbertTransform.class);
private float[] mHilbertFilter;
private float[] mBuffer;
private int mBufferSize;
private int mBufferPointer;
private int[][] mIndexMap;
private int mMapHeight;
private int mCenterTapIndex;
private float mCenterCoefficent;
private boolean mInvertFlag = false;
/**
* Hilbert transform filter used for converting real-valued samples into
* complex valued samples using frequency translation (FS/4) and a half-band
* filter.
*
* This filter uses a circular sample buffer and a pre-calculated
* index map to correctly map the filter coefficients to the circular delay
* buffer contents as each new sample is added to the buffer and processed.
*
* Half-band filter coefficients used in this filter must be of length N
* where (N + 1) is a multiple of 4. This filter uses a pre-defined half
* band filter with 47 coefficients.
*
* This transform process is described in Understanding Digital Signal
* Processing, Lyons, 3e, 2011, sections 13.1.2 and 13.1.3 (p 674-678) and
* implemented as described in Section 13.37.1 and 13.37.2 (p 802-804)
*/
public LegacyHilbertTransform()
{
convertHalfBandToHilbert(HilbertTransform.HALF_BAND_FILTER_47_TAP);
mBufferSize = mHilbertFilter.length + 1;
mBuffer = new float[mBufferSize];
mCenterCoefficent = mHilbertFilter[mHilbertFilter.length / 2];
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;
for(int y = 0; y < samples.length; y += 2)
{
insert(samples[y]);
insert(samples[y + 1]);
accumulator = 0.0f;
int index = mBufferPointer / 2;
for(int x = 0; x < mHilbertFilter.length / 2; x += 2)
{
accumulator += mHilbertFilter[x] *
(mBuffer[mIndexMap[index][x + 1]] -
mBuffer[mIndexMap[index][x]]);
}
//Perform FS/2 frequency translation on the final filtered values
if(mInvertFlag)
{
//inphase
samples[y] = -(mBuffer[mIndexMap[index][mCenterTapIndex]]);
//quadrature
samples[y + 1] = -accumulator;
}
else
{
//inphase
samples[y] = mBuffer[mIndexMap[index][mCenterTapIndex]];
//quadrature
samples[y + 1] = accumulator;
}
mInvertFlag = !mInvertFlag;
}
return samples;
}
/**
* Inserts the sample into the circular buffer, overwriting the oldest value
*/
private void insert(float sample)
{
mBuffer[mBufferPointer++] = sample;
mBufferPointer = mBufferPointer % mBufferSize;
}
/**
* Creates an index map to support efficient lookup of sample indexes from
* the circular buffer.
*
* The first index of the map corresponds to the current buffer pointer
* setting.
*
* The second index is structured in sample pairs to align with
* the hilbert filter coefficients. For example, the first iteration of
* the filter would use filter coefficient 0 and the index map values 0 and
* 1 would be pointing to samples 46 and 0.
*
* The final element of each index set is the index of the center sample.
*/
private void generateIndexMap(int size)
{
mMapHeight = size / 2 + 1;
int mapWidth = mMapHeight + 1;
mIndexMap = new int[mMapHeight][mapWidth];
//Setup the first row with buffer pointer index 0 as a starting point
for(int x = 0; x < mapWidth - 1; x += 2)
{
mIndexMap[0][x] = size - 1 - x;
mIndexMap[0][x + 1] = x;
}
//Place the center index at the end of the array
mCenterTapIndex = mapWidth - 1;
mIndexMap[0][mCenterTapIndex] = size / 2;
//For each subsequent row, increment the previous row's value by 2,
//wrapping as needed, to keep the values between 0 and size - 1
for(int x = 1; x < mMapHeight; x++)
{
for(int y = 0; y < mapWidth; y++)
{
mIndexMap[x][y] = mIndexMap[x - 1][y] + 2;
if(mIndexMap[x][y] >= size)
{
mIndexMap[x][y] -= size + 1;
//Special handling for center tap wrap around
if(y == mCenterTapIndex && mIndexMap[x][y] < 0)
{
mIndexMap[x][y] = size;
}
}
}
}
}
/**
* Logs the index map contents
*/
public void logIndexMap()
{
for(int[] indexSet : mIndexMap)
{
mLog.debug("Row:" + Arrays.toString(indexSet));
}
}
/**
* Converts the half-band filter coefficients for use as hilbert transform
* filter coefficients. Sets all even-numbered coefficients left of center
* to negative and all even-numbered coefficients right of center to positive
* which applies a FS/4 frequency translation to the coefficients.
*
* Note: even though the coefficients above the center coefficient are being
* defined, they are unused during the convolution process since we're using
* a folded FIR structure to exploit the symmetric nature of the half-band
* filter coefficients. The polarity difference between the upper and lower
* halves is accounted for in the filter() method.
*
* 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 * -FastMath.abs(coefficients[x]);
}
else if(x > middle)
{
mHilbertFilter[x] = 2.0f * FastMath.abs(coefficients[x]);
}
else
{
mHilbertFilter[x] = 2.0f * coefficients[x];
}
}
}
}

View File

@@ -23,7 +23,6 @@ import io.github.dsheirer.dsp.oscillator.IRealOscillator;
import io.github.dsheirer.dsp.oscillator.OscillatorFactory;
import io.github.dsheirer.sample.SampleUtils;
import io.github.dsheirer.sample.complex.ComplexSamples;
import java.util.Arrays;
/**
@@ -35,9 +34,10 @@ public class ScalarHilbertTransform extends HilbertTransform
/**
* Filters the real samples array into complex samples at half the sample rate.
* @param realSamples to filter
* @param timestamp of the first sample
* @return complex samples at half the sample rate
*/
public ComplexSamples filter(float[] realSamples)
public ComplexSamples filter(float[] realSamples, long timestamp)
{
int bufferLength = realSamples.length / 2;
@@ -52,7 +52,7 @@ public class ScalarHilbertTransform extends HilbertTransform
mQBuffer = qTemp;
}
ComplexSamples deinterleaved = SampleUtils.deinterleave(realSamples);
ComplexSamples deinterleaved = SampleUtils.deinterleave(realSamples, timestamp);
System.arraycopy(deinterleaved.i(), 0, mIBuffer, mIOverlap, deinterleaved.i().length);
System.arraycopy(deinterleaved.q(), 0, mQBuffer, mQOverlap, deinterleaved.q().length);
@@ -78,7 +78,7 @@ public class ScalarHilbertTransform extends HilbertTransform
System.arraycopy(mIBuffer, mIBuffer.length - mIOverlap, mIBuffer, 0, mIOverlap);
System.arraycopy(mQBuffer, mQBuffer.length - mQOverlap, mQBuffer, 0, mQOverlap);
return new ComplexSamples(i, q);
return new ComplexSamples(i, q, timestamp);
}
public static void main(String[] args)
@@ -104,8 +104,8 @@ public class ScalarHilbertTransform extends HilbertTransform
if(validate)
{
ComplexSamples scalar = hilbertTransform.filter(realSamples);
ComplexSamples vector = vector512.filter(realSamples);
ComplexSamples scalar = hilbertTransform.filter(realSamples, 0l);
ComplexSamples vector = vector512.filter(realSamples, start);
System.out.println("SI: " + Arrays.toString(scalar.i()));
System.out.println("VI: " + Arrays.toString(vector.i()));
@@ -119,7 +119,7 @@ public class ScalarHilbertTransform extends HilbertTransform
{
// ComplexSamples filtered = hilbertTransform.filter(realSamples);
// ComplexSamples filtered = vectorDefault.filter(realSamples);
ComplexSamples filtered = vector512.filter(realSamples);
ComplexSamples filtered = vector512.filter(realSamples, start);
// ComplexSamples filtered = vector256.filter(realSamples);
// ComplexSamples filtered = vector128.filter(realSamples);
// ComplexSamples filtered = vector64.filter(realSamples);

View File

@@ -37,9 +37,10 @@ public class VectorHilbertTransform128Bits extends HilbertTransform
/**
* Filters the real samples array into complex samples at half the sample rate.
* @param realSamples to filter
* @param timestamp of the first sample
* @return complex samples at half the sample rate
*/
public ComplexSamples filter(float[] realSamples)
public ComplexSamples filter(float[] realSamples, long timestamp)
{
int bufferLength = realSamples.length / 2;
@@ -54,7 +55,7 @@ public class VectorHilbertTransform128Bits extends HilbertTransform
mQBuffer = qTemp;
}
ComplexSamples deinterleaved = SampleUtils.deinterleave(realSamples);
ComplexSamples deinterleaved = SampleUtils.deinterleave(realSamples, timestamp);
VectorUtilities.checkComplexArrayLength(deinterleaved.i(), deinterleaved.q(), VECTOR_SPECIES);
System.arraycopy(deinterleaved.i(), 0, mIBuffer, mIOverlap, deinterleaved.i().length);
@@ -91,6 +92,6 @@ public class VectorHilbertTransform128Bits extends HilbertTransform
System.arraycopy(mIBuffer, mIBuffer.length - mIOverlap, mIBuffer, 0, mIOverlap);
System.arraycopy(mQBuffer, mQBuffer.length - mQOverlap, mQBuffer, 0, mQOverlap);
return new ComplexSamples(i, q);
return new ComplexSamples(i, q, timestamp);
}
}

View File

@@ -37,9 +37,10 @@ public class VectorHilbertTransform256Bits extends HilbertTransform
/**
* Filters the real samples array into complex samples at half the sample rate.
* @param realSamples to filter
* @param timestamp of the first sample
* @return complex samples at half the sample rate
*/
public ComplexSamples filter(float[] realSamples)
public ComplexSamples filter(float[] realSamples, long timestamp)
{
int bufferLength = realSamples.length / 2;
@@ -54,7 +55,7 @@ public class VectorHilbertTransform256Bits extends HilbertTransform
mQBuffer = qTemp;
}
ComplexSamples deinterleaved = SampleUtils.deinterleave(realSamples);
ComplexSamples deinterleaved = SampleUtils.deinterleave(realSamples, timestamp);
VectorUtilities.checkComplexArrayLength(deinterleaved.i(), deinterleaved.q(), VECTOR_SPECIES);
System.arraycopy(deinterleaved.i(), 0, mIBuffer, mIOverlap, deinterleaved.i().length);
@@ -85,6 +86,6 @@ public class VectorHilbertTransform256Bits extends HilbertTransform
System.arraycopy(mIBuffer, mIBuffer.length - mIOverlap, mIBuffer, 0, mIOverlap);
System.arraycopy(mQBuffer, mQBuffer.length - mQOverlap, mQBuffer, 0, mQOverlap);
return new ComplexSamples(i, q);
return new ComplexSamples(i, q, timestamp);
}
}

View File

@@ -22,12 +22,11 @@ package io.github.dsheirer.dsp.filter.hilbert;
import io.github.dsheirer.sample.SampleUtils;
import io.github.dsheirer.sample.complex.ComplexSamples;
import io.github.dsheirer.vector.VectorUtilities;
import java.util.Arrays;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
import java.util.Arrays;
/**
* Implements a Hilbert transform using vector SIMD operations against a pre-defined 47-tap filter.
* As described in Understanding Digital Signal Processing, Lyons, 3e, 2011, Section 13.37.2 (p 804-805)
@@ -53,9 +52,10 @@ public class VectorHilbertTransform512Bits extends HilbertTransform
/**
* Filters the real samples array into complex samples at half the sample rate.
* @param realSamples to filter
* @param timestamp of the first sample
* @return complex samples at half the sample rate
*/
public ComplexSamples filter(float[] realSamples)
public ComplexSamples filter(float[] realSamples, long timestamp)
{
int bufferLength = realSamples.length / 2;
@@ -70,7 +70,7 @@ public class VectorHilbertTransform512Bits extends HilbertTransform
mQBuffer = qTemp;
}
ComplexSamples deinterleaved = SampleUtils.deinterleave(realSamples);
ComplexSamples deinterleaved = SampleUtils.deinterleave(realSamples, timestamp);
VectorUtilities.checkComplexArrayLength(deinterleaved.i(), deinterleaved.q(), VECTOR_SPECIES);
System.arraycopy(deinterleaved.i(), 0, mIBuffer, mIOverlap, deinterleaved.i().length);
@@ -103,6 +103,6 @@ public class VectorHilbertTransform512Bits extends HilbertTransform
System.arraycopy(mIBuffer, mIBuffer.length - mIOverlap, mIBuffer, 0, mIOverlap);
System.arraycopy(mQBuffer, mQBuffer.length - mQOverlap, mQBuffer, 0, mQOverlap);
return new ComplexSamples(i, q);
return new ComplexSamples(i, q, timestamp);
}
}

View File

@@ -37,9 +37,10 @@ public class VectorHilbertTransform64Bits extends HilbertTransform
/**
* Filters the real samples array into complex samples at half the sample rate.
* @param realSamples to filter
* @param timestamp of the first sample
* @return complex samples at half the sample rate
*/
public ComplexSamples filter(float[] realSamples)
public ComplexSamples filter(float[] realSamples, long timestamp)
{
int bufferLength = realSamples.length / 2;
@@ -54,7 +55,7 @@ public class VectorHilbertTransform64Bits extends HilbertTransform
mQBuffer = qTemp;
}
ComplexSamples deinterleaved = SampleUtils.deinterleave(realSamples);
ComplexSamples deinterleaved = SampleUtils.deinterleave(realSamples, timestamp);
VectorUtilities.checkComplexArrayLength(deinterleaved.i(), deinterleaved.q(), VECTOR_SPECIES);
System.arraycopy(deinterleaved.i(), 0, mIBuffer, mIOverlap, deinterleaved.i().length);
@@ -103,6 +104,6 @@ public class VectorHilbertTransform64Bits extends HilbertTransform
System.arraycopy(mIBuffer, mIBuffer.length - mIOverlap, mIBuffer, 0, mIOverlap);
System.arraycopy(mQBuffer, mQBuffer.length - mQOverlap, mQBuffer, 0, mQOverlap);
return new ComplexSamples(i, q);
return new ComplexSamples(i, q, timestamp);
}
}

View File

@@ -37,9 +37,10 @@ public class VectorHilbertTransformDefaultBits extends HilbertTransform
/**
* Filters the real samples array into complex samples at half the sample rate.
* @param realSamples to filter
* @param timestamp of the first sample
* @return complex samples at half the sample rate
*/
public ComplexSamples filter(float[] realSamples)
public ComplexSamples filter(float[] realSamples, long timestamp)
{
int bufferLength = realSamples.length / 2;
@@ -54,7 +55,7 @@ public class VectorHilbertTransformDefaultBits extends HilbertTransform
mQBuffer = qTemp;
}
ComplexSamples deinterleaved = SampleUtils.deinterleave(realSamples);
ComplexSamples deinterleaved = SampleUtils.deinterleave(realSamples, timestamp);
VectorUtilities.checkComplexArrayLength(deinterleaved.i(), deinterleaved.q(), VECTOR_SPECIES);
System.arraycopy(deinterleaved.i(), 0, mIBuffer, mIOverlap, deinterleaved.i().length);
@@ -84,6 +85,6 @@ public class VectorHilbertTransformDefaultBits extends HilbertTransform
System.arraycopy(mIBuffer, mIBuffer.length - mIOverlap, mIBuffer, 0, mIOverlap);
System.arraycopy(mQBuffer, mQBuffer.length - mQOverlap, mQBuffer, 0, mQOverlap);
return new ComplexSamples(i, q);
return new ComplexSamples(i, q, timestamp);
}
}

View File

@@ -1,3 +1,22 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.gain.complex;
import io.github.dsheirer.sample.complex.ComplexSamples;
@@ -46,7 +65,7 @@ public abstract class ComplexGain
*/
public ComplexSamples apply(ComplexSamples samples)
{
return apply(samples.i(), samples.q());
return apply(samples.i(), samples.q(), samples.timestamp());
}
/**
@@ -56,7 +75,8 @@ public abstract class ComplexGain
* original sample buffer.
* @param i samples to amplify
* @param q samples to amplify
* @param timestamp of the first sample
* @return amplified samples
*/
public abstract ComplexSamples apply(float[] i, float q[]);
public abstract ComplexSamples apply(float[] i, float q[], long timestamp);
}

View File

@@ -18,13 +18,9 @@
*/
package io.github.dsheirer.dsp.gain.complex;
import io.github.dsheirer.sample.SampleUtils;
import io.github.dsheirer.sample.complex.Complex;
import io.github.dsheirer.sample.complex.ComplexSamples;
import java.text.DecimalFormat;
import java.util.Arrays;
public class ComplexGainControl implements IComplexGainControl
{
public static final float OBJECTIVE_ENVELOPE = 1.0f;
@@ -48,9 +44,10 @@ public class ComplexGainControl implements IComplexGainControl
* gain for the single sample in the buffer that has the largest envelope.
* @param i samples
* @param q samples
* @param timestamp of the first sample
* @return processed/amplified buffer
*/
@Override public ComplexSamples process(float[] i, float[] q)
@Override public ComplexSamples process(float[] i, float[] q, long timestamp)
{
float maxEnvelope = MINIMUM_ENVELOPE;
@@ -70,45 +67,6 @@ public class ComplexGainControl implements IComplexGainControl
qProcessed[x] = q[x] * gain;
}
return new ComplexSamples(iProcessed, qProcessed);
}
public static void main(String[] args)
{
int sampleSize = 2048;
ComplexSamples samples = SampleUtils.generateComplexRandomVectorLength(sampleSize);
ComplexGainControl scalar = new ComplexGainControl();
VectorComplexGainControl vector = new VectorComplexGainControl();
boolean validate = false;
if(validate)
{
ComplexSamples scalarSamples = scalar.process(samples.i(), samples.q());
ComplexSamples vectorSamples = vector.process(samples.i(), samples.q());
System.out.println("SCALAR I: " + Arrays.toString(scalarSamples.i()));
System.out.println("VECTOR I: " + Arrays.toString(vectorSamples.i()));
System.out.println("SCALAR Q: " + Arrays.toString(scalarSamples.q()));
System.out.println("VECTOR Q: " + Arrays.toString(vectorSamples.q()));
}
else
{
double accumulator = 0.0d;
int iterations = 10_000_000;
long start = System.currentTimeMillis();
for(int x = 0; x < iterations; x++)
{
// ComplexSamples processed = scalar.process(samples.i(), samples.q());
ComplexSamples processed = vector.process(samples.i(), samples.q());
accumulator += processed.i()[3];
}
double elapsed = System.currentTimeMillis() - start;
DecimalFormat df = new DecimalFormat("0.000");
System.out.println("Accumulator: " + accumulator);
System.out.println("Test Complete. Elapsed Time: " + df.format(elapsed / 1000.0d) + " seconds");
}
return new ComplexSamples(iProcessed, qProcessed, timestamp);
}
}

View File

@@ -1,3 +1,22 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.gain.complex;
import io.github.dsheirer.sample.complex.ComplexSamples;
@@ -13,5 +32,5 @@ public interface IComplexGainControl
* @param q samples
* @return amplified gain samples
*/
ComplexSamples process(float[] i, float[] q);
ComplexSamples process(float[] i, float[] q, long timestamp);
}

View File

@@ -1,3 +1,22 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.gain.complex;
import io.github.dsheirer.sample.complex.ComplexSamples;
@@ -22,7 +41,7 @@ public class ScalarComplexGain extends ComplexGain
* @param q samples to amplify
* @return amplified samples
*/
@Override public ComplexSamples apply(float[] i, float[] q)
@Override public ComplexSamples apply(float[] i, float[] q, long timestamp)
{
for(int x = 0; x < i.length; x++)
{
@@ -30,6 +49,6 @@ public class ScalarComplexGain extends ComplexGain
q[x] *= mGain;
}
return new ComplexSamples(i, q);
return new ComplexSamples(i, q, timestamp);
}
}

View File

@@ -1,3 +1,22 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.gain.complex;
import io.github.dsheirer.sample.complex.ComplexSamples;
@@ -27,7 +46,7 @@ public class VectorComplexGain extends ComplexGain
* @param q samples to amplify
* @return amplified samples.
*/
@Override public ComplexSamples apply(float[] i, float[] q)
@Override public ComplexSamples apply(float[] i, float[] q, long timestamp)
{
VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES);
@@ -37,6 +56,6 @@ public class VectorComplexGain extends ComplexGain
FloatVector.fromArray(VECTOR_SPECIES, q, x).mul(mGain).intoArray(q, x);
}
return new ComplexSamples(i, q);
return new ComplexSamples(i, q, timestamp);
}
}

View File

@@ -1,3 +1,22 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.gain.complex;
import io.github.dsheirer.sample.complex.ComplexSamples;
@@ -18,7 +37,7 @@ public class VectorComplexGainControl implements IComplexGainControl
private static final float MINIMUM_ENVELOPE = 0.0001f;
private static final float ENVELOPE_ESTIMATE = 0.4f;
@Override public ComplexSamples process(float[] i, float[] q)
@Override public ComplexSamples process(float[] i, float[] q, long timestamp)
{
VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES);
@@ -47,6 +66,6 @@ public class VectorComplexGainControl implements IComplexGainControl
FloatVector.fromArray(VECTOR_SPECIES, q, bufferPointer).mul(gain).intoArray(qProcessed, bufferPointer);
}
return new ComplexSamples(iProcessed, qProcessed);
return new ComplexSamples(iProcessed, qProcessed, timestamp);
}
}

View File

@@ -84,11 +84,12 @@ public abstract class ComplexMixer
/**
* Generates complex samples from the underlying oscillator
* @param sampleCount to generate
* @param timestamp of the first sample
* @return complex samples
*/
protected ComplexSamples generate(int sampleCount)
protected ComplexSamples generate(int sampleCount, long timestamp)
{
return mOscillator.generateComplexSamples(sampleCount);
return mOscillator.generateComplexSamples(sampleCount, timestamp);
}
/**
@@ -98,7 +99,7 @@ public abstract class ComplexMixer
*/
public ComplexSamples mix(ComplexSamples samples)
{
return mix(samples.i(), samples.q());
return mix(samples.i(), samples.q(), samples.timestamp());
}
/**
@@ -117,7 +118,5 @@ public abstract class ComplexMixer
* @param q complex samples to mix
* @return mixed samples
*/
public abstract ComplexSamples mix(float[] i, float[] q);
public abstract ComplexSamples mix(float[] i, float[] q, long timestamp);
}

View File

@@ -52,11 +52,12 @@ public class ScalarComplexMixer extends ComplexMixer
* Mixes the complex I & Q samples with samples generated from an oscillator.
* @param iSamples complex samples to mix
* @param qSamples complex samples to mix
* @param timestamp of the first sample
* @return mixed samples
*/
@Override public ComplexSamples mix(float[] iSamples, float[] qSamples)
@Override public ComplexSamples mix(float[] iSamples, float[] qSamples, long timestamp)
{
ComplexSamples mixer = generate(iSamples.length);
ComplexSamples mixer = generate(iSamples.length, timestamp);
float[] iMixer = mixer.i();
float[] qMixer = mixer.q();
@@ -82,7 +83,7 @@ public class ScalarComplexMixer extends ComplexMixer
@Override
public ComplexSamples mix(InterleavedComplexSamples samples)
{
ComplexSamples mixer = generate(samples.samples().length / 2);
ComplexSamples mixer = generate(samples.samples().length / 2, samples.timestamp());
float[] iMixer = mixer.i();
float[] qMixer = mixer.q();

View File

@@ -1,3 +1,22 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.mixer;
import io.github.dsheirer.sample.complex.ComplexSamples;
@@ -26,14 +45,15 @@ public class VectorComplexMixer extends ComplexMixer
* Mixes the complex I & Q samples with samples generated from an oscillator.
* @param iSamples complex samples to mix
* @param qSamples complex samples to mix
* @param timestamp for the first sample
* @return mixed samples
*/
@Override public ComplexSamples mix(float[] iSamples, float[] qSamples)
@Override public ComplexSamples mix(float[] iSamples, float[] qSamples, long timestamp)
{
VectorUtilities.checkComplexArrayLength(iSamples, qSamples, VECTOR_SPECIES);
//Reuse this complex samples buffer to store the results and return to caller
ComplexSamples mixer = generate(iSamples.length);
ComplexSamples mixer = generate(iSamples.length, timestamp);
float[] iMixer = mixer.i();
float[] qMixer = mixer.q();

View File

@@ -33,7 +33,8 @@ public interface IComplexOscillator extends IOscillator
/**
* Generates the specified number of complex samples into a complex samples object.
* @param sampleCount number of complex samples to generate.
* @param timestamp for the first sample
* @return generated samples
*/
ComplexSamples generateComplexSamples(int sampleCount);
ComplexSamples generateComplexSamples(int sampleCount, long timestamp);
}

View File

@@ -96,7 +96,7 @@ public class ScalarComplexOscillator extends AbstractOscillator implements IComp
}
@Override
public ComplexSamples generateComplexSamples(int sampleCount)
public ComplexSamples generateComplexSamples(int sampleCount, long timestamp)
{
float[] iSamples = new float[sampleCount];
float[] qSamples = new float[sampleCount];
@@ -133,6 +133,6 @@ public class ScalarComplexOscillator extends AbstractOscillator implements IComp
mPreviousInphase = previousInphase;
mPreviousQuadrature = previousQuadrature;
return new ComplexSamples(iSamples, qSamples);
return new ComplexSamples(iSamples, qSamples, timestamp);
}
}

View File

@@ -21,14 +21,13 @@ package io.github.dsheirer.dsp.oscillator;
import io.github.dsheirer.sample.complex.ComplexSamples;
import io.github.dsheirer.vector.VectorUtilities;
import java.util.Arrays;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorSpecies;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
/**
* Complex oscillator that uses JDK17 SIMD vector operations to generate complex sample arrays.
*
@@ -145,9 +144,10 @@ public class VectorComplexOscillator extends AbstractOscillator implements IComp
/**
* Generates complex samples.
* @param sampleCount number of samples to generate and length of the resulting float array.
* @param timestamp of the first sample
* @return generated samples
*/
@Override public ComplexSamples generateComplexSamples(int sampleCount)
@Override public ComplexSamples generateComplexSamples(int sampleCount, long timestamp)
{
if(sampleCount % VECTOR_SPECIES.length() != 0)
{
@@ -184,6 +184,6 @@ public class VectorComplexOscillator extends AbstractOscillator implements IComp
previousInphase.intoArray(mPreviousInphases, 0);
previousQuadrature.intoArray(mPreviousQuadratures, 0);
return new ComplexSamples(iSamples, qSamples);
return new ComplexSamples(iSamples, qSamples, timestamp);
}
}