mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
#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:
@@ -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
|
||||
|
||||
@@ -156,7 +156,7 @@ public class TwoChannelSynthesizerM2
|
||||
mTopBlockFlag = !mTopBlockFlag;
|
||||
}
|
||||
|
||||
return new ComplexSamples(i, q);
|
||||
return new ComplexSamples(i, q, channelBuffer1.timestamp());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -90,7 +90,7 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
|
||||
|
||||
if(mMixerAssembler.hasBuffer())
|
||||
{
|
||||
ComplexSamples buffer = mMixerAssembler.getBuffer();
|
||||
ComplexSamples buffer = mMixerAssembler.getBuffer(getCurrentSampleTimestamp());
|
||||
|
||||
if(mComplexSamplesListener != null)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user