#2571 Use jAirspy library instead of legacy custom driver code (#2573)

Co-authored-by: sheirerd <sheirerd@ainfosec.com>
This commit is contained in:
Denny Sheirer
2026-11-04 07:00:44 -05:00
committed by GitHub
parent e8e813142b
commit 69d49d9dd0
28 changed files with 493 additions and 3450 deletions

View File

@@ -125,6 +125,7 @@ dependencies {
implementation 'commons-io:commons-io:2.22.0'
implementation 'eu.hansolo:charts:1.0.5'
implementation 'io.github.dsheirer:jbladerf:2.6.0' //bladeRF tuners - GitHub release, see settings.gradle
implementation 'io.github.dsheirer:jairspy:1.0.12' //Airspy tuners - GitHub release, see settings.gradle
implementation 'io.github.dsheirer:jhydrasdr:1.1.3' //HydraSDR tuners - GitHub release, see settings.gradle
implementation 'io.github.dsheirer:radio-reference-api:18.0.0'
implementation 'io.github.dsheirer:usb4java-native-libraries:1.3.3' //OSX & Windows aarch64 native libs

View File

@@ -9,6 +9,14 @@ dependencyResolutionManagement {
mavenCentral()
maven { url 'https://jitpack.io' }
//jAirspy is published as a GitHub release, with the native libraries for all platforms in the jar
ivy {
url = 'https://github.com/DSheirer/jAirspy/releases/download'
patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' }
metadataSources { artifact() }
content { includeModule 'io.github.dsheirer', 'jairspy' }
}
//jHydraSDR is published as a GitHub release, with the native libraries for all platforms in the jar
ivy {
url = 'https://github.com/DSheirer/jHydraSDR/releases/download'

View File

@@ -1,40 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 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.buffer.sample;
import java.nio.ByteBuffer;
/**
* Interface to convert samples from a byte buffer to an array of shorts.
*/
public interface ISampleConverter
{
/**
* Converts the byte samples contained in the byte buffer to their short-valued representation
* @param buffer of samples
* @return converted samples
*/
short[] convert(ByteBuffer buffer);
/**
* Current DC average of the samples that have been processed thus far.
*/
float getAverageDc();
}

View File

@@ -1,119 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 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.buffer.sample;
import io.github.dsheirer.dsp.filter.hilbert.HilbertTransform;
import java.util.Iterator;
/**
* Base complex samples iterator for raw sample buffers. Incorporates a Hilbert transform filter
* for converting the real sample array to complex samples.
* @param <T> either ComplexSamples or InterleavedComplexSamples
*/
public abstract class SampleBufferIterator<T> implements Iterator<T>
{
protected static final float SCALE_SIGNED_12_BIT_TO_FLOAT = 1.0f / 2048.0f;
protected static final int FRAGMENT_SIZE = 4096;
protected static final float[] COEFFICIENTS =
HilbertTransform.convertHalfBandToHilbert(HilbertTransform.HALF_BAND_FILTER_47_TAP);
public static final int I_OVERLAP = 11;
public static final int Q_OVERLAP = 23;
protected float[] mIBuffer = new float[FRAGMENT_SIZE + I_OVERLAP];
protected float[] mQBuffer = new float[FRAGMENT_SIZE + Q_OVERLAP];
protected short[] mSamples;
protected int mSamplesPointer = 0;
protected float mAverageDc;
private long mTimestamp;
private float mSamplesPerMillisecond;
/**
* Constructs an instance
* @param samples from the tuner.
* @param residualI samples from previous buffer
* @param residualQ samples from previous buffer
* @param averageDc as measured
* @param timestamp of the buffer
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public SampleBufferIterator(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp,
float samplesPerMillisecond)
{
if(residualI.length != I_OVERLAP || residualQ.length != Q_OVERLAP)
{
throw new IllegalArgumentException("Residual I length [" + residualI.length +
"] must be " + I_OVERLAP + " and Residual Q length [" + residualQ.length +
"] must be " + Q_OVERLAP);
}
int requiredInterval = FRAGMENT_SIZE * 2; //requires 4 bytes (2 samples) per fragment
if(samples.length % requiredInterval != 0)
{
throw new IllegalArgumentException("Samples short array length [" + mSamples.length +
"]must be an integer multiple of " + requiredInterval);
}
mAverageDc = averageDc;
mSamples = samples;
mTimestamp = timestamp;
mSamplesPerMillisecond = samplesPerMillisecond;
//Transfer and scale the residual I & Q samples from the previous buffer
for(int i = 0; i < residualI.length; i++)
{
mIBuffer[i] = scale(residualI[i], averageDc);
}
for(int q = 0; q < residualQ.length; q++)
{
mQBuffer[q] = scale(residualQ[q], averageDc);
}
}
/**
* Calculates the timestamp for a samples buffer fragment based on where it is extracted from relative to the
* native buffer starting timestamp.
* @param samplesPointer for the start of the fragment. Note: this value will be divided by 2 to account for I/Q sample pairs.
* @return timestamp adjusted to the fragment or sub-buffer start sample.
*/
protected long getFragmentTimestamp(int samplesPointer)
{
return mTimestamp + (long)(samplesPointer / 2 / mSamplesPerMillisecond);
}
@Override
public boolean hasNext()
{
return mSamplesPointer < mSamples.length;
}
/**
* Scales the short sample value to a 12-bit floating point and subtracts the average DC offset
* @param value to scale
* @param averageDc detected
* @return scaled and normalized sample
*/
public static float scale(short value, float averageDc)
{
return ((float)value - 2048) * SCALE_SIGNED_12_BIT_TO_FLOAT - averageDc;
}
}

View File

@@ -1,98 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 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.buffer.sample;
import io.github.dsheirer.sample.complex.ComplexSamples;
import java.util.Arrays;
/**
* Scalar implementation for non-packed native buffers
*/
public class SampleBufferIteratorScalar extends SampleBufferIterator<ComplexSamples>
{
/**
* Constructs an instance
*
* @param samples from the device, either packed or unpacked.
* @param residualI samples from last buffer
* @param residualQ samples from last buffer
* @param averageDc measured
* @param timestamp of the buffer
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public SampleBufferIteratorScalar(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
}
@Override
public ComplexSamples next()
{
if(mSamplesPointer >= mSamples.length)
{
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
int offset = mSamplesPointer;
for(int x = 0; x < FRAGMENT_SIZE; x++)
{
mIBuffer[x + I_OVERLAP] = scale(mSamples[offset++], mAverageDc);
mQBuffer[x + Q_OVERLAP] = scale(mSamples[offset++], mAverageDc);
}
mSamplesPointer = offset;
//The i line is a simple delay line, so we'll just copy those as a new array here.
float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE);
float[] q = new float[FRAGMENT_SIZE];
float accumulator;
for(int x = 0; x < FRAGMENT_SIZE; x++)
{
accumulator = 0;
for(int tap = 0; tap < COEFFICIENTS.length; tap++)
{
accumulator += COEFFICIENTS[tap] * mQBuffer[x + tap];
}
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
if(x % 2 == 0)
{
q[x] = accumulator;
}
else
{
i[x] = -i[x];
q[x] = -accumulator;
}
}
//Copy residual end samples to beginning of buffers for the next iteration
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
return new ComplexSamples(i, q, timestamp);
}
}

View File

@@ -1,128 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 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.buffer.sample;
import io.github.dsheirer.sample.complex.ComplexSamples;
import java.util.Arrays;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Vector SIMD implementation for non-packed native buffers
*/
public class SampleBufferIteratorVector128Bits extends SampleBufferIterator<ComplexSamples>
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_128;
/**
* Constructs an instance
*
* @param samples from the device, either packed or unpacked.
* @param residualI samples from last buffer
* @param residualQ samples from last buffer
* @param averageDc measured
* @param timestamp of the buffer
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public SampleBufferIteratorVector128Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
}
@Override
public ComplexSamples next()
{
if(mSamplesPointer >= mSamples.length)
{
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
int offset = mSamplesPointer;
int fragmentPointer = 0;
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
while(fragmentPointer < FRAGMENT_SIZE)
{
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
{
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
scaledSamples[y] = mSamples[offset++];
}
//SIMD scaling operation
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
.sub(2048.0f)
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
.sub(mAverageDc)
.intoArray(scaledSamples, 0);
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
{
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
}
}
mSamplesPointer = offset;
//The i line is a simple delay line, so we'll just copy those as a new array here.
float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE);
float[] q = new float[FRAGMENT_SIZE];
FloatVector accumulator;
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 4);
FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8);
FloatVector f4 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 12);
FloatVector f5 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16);
FloatVector f6 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 20);
for(int x = 0; x < FRAGMENT_SIZE; x++)
{
accumulator = FloatVector.zero(VECTOR_SPECIES);
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator);
accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
accumulator = f4.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator);
accumulator = f5.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator);
accumulator = f6.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator);
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
if(x % 2 == 0)
{
q[x] = accumulator.reduceLanes(VectorOperators.ADD);
}
else
{
i[x] = -i[x];
q[x] = -accumulator.reduceLanes(VectorOperators.ADD);
}
}
//Copy residual end samples to beginning of buffers for the next iteration
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
return new ComplexSamples(i, q, timestamp);
}
}

View File

@@ -1,122 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 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.buffer.sample;
import io.github.dsheirer.sample.complex.ComplexSamples;
import java.util.Arrays;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Vector SIMD implementation for non-packed native buffers
*/
public class SampleBufferIteratorVector256Bits extends SampleBufferIterator<ComplexSamples>
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_256;
/**
* Constructs an instance
*
* @param samples from the device, either packed or unpacked.
* @param residualI samples from last buffer
* @param residualQ samples from last buffer
* @param averageDc measured
* @param timestamp of the buffer
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public SampleBufferIteratorVector256Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
}
@Override
public ComplexSamples next()
{
if(mSamplesPointer >= mSamples.length)
{
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
int offset = mSamplesPointer;
int fragmentPointer = 0;
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
while(fragmentPointer < FRAGMENT_SIZE)
{
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
{
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
scaledSamples[y] = mSamples[offset++];
}
//SIMD scaling operation
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
.sub(2048.0f)
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
.sub(mAverageDc)
.intoArray(scaledSamples, 0);
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
{
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
}
}
mSamplesPointer = offset;
//The i line is a simple delay line, so we'll just copy those as a new array here.
float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE);
float[] q = new float[FRAGMENT_SIZE];
FloatVector accumulator;
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8);
FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16);
for(int x = 0; x < FRAGMENT_SIZE; x++)
{
accumulator = FloatVector.zero(VECTOR_SPECIES);
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator);
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
if(x % 2 == 0)
{
q[x] = accumulator.reduceLanes(VectorOperators.ADD);
}
else
{
i[x] = -i[x];
q[x] = -accumulator.reduceLanes(VectorOperators.ADD);
}
}
//Copy residual end samples to beginning of buffers for the next iteration
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
return new ComplexSamples(i, q, timestamp);
}
}

View File

@@ -1,125 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 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.buffer.sample;
import io.github.dsheirer.sample.complex.ComplexSamples;
import java.util.Arrays;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Vector SIMD implementation for non-packed native buffers
*/
public class SampleBufferIteratorVector512Bits extends SampleBufferIterator<ComplexSamples>
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_512;
private float[] mFilterPart2;
/**
* Constructs an instance
*
* @param samples from the device, either packed or unpacked.
* @param residualI samples from last buffer
* @param residualQ samples from last buffer
* @param averageDc measured
* @param timestamp of the buffer
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public SampleBufferIteratorVector512Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
//Arrange the last 8 taps of the filter into the higher indices of an array to align for SIMD
mFilterPart2 = new float[16];
System.arraycopy(COEFFICIENTS, 16, mFilterPart2, 8, 8);
}
@Override
public ComplexSamples next()
{
if(mSamplesPointer >= mSamples.length)
{
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
int offset = mSamplesPointer;
int fragmentPointer = 0;
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
while(fragmentPointer < FRAGMENT_SIZE)
{
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
{
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
scaledSamples[y] = mSamples[offset++];
}
//SIMD scaling operation
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
.sub(2048.0f)
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
.sub(mAverageDc)
.intoArray(scaledSamples, 0);
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
{
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
}
}
mSamplesPointer = offset;
//The i line is a simple delay line, so we'll just copy those as a new array here.
float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE);
float[] q = new float[FRAGMENT_SIZE];
FloatVector accumulator;
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, mFilterPart2, 0);
for(int x = 0; x < FRAGMENT_SIZE; x++)
{
accumulator = FloatVector.zero(VECTOR_SPECIES);
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
if(x % 2 == 0)
{
q[x] = accumulator.reduceLanes(VectorOperators.ADD);
}
else
{
i[x] = -i[x];
q[x] = -accumulator.reduceLanes(VectorOperators.ADD);
}
}
//Copy residual end samples to beginning of buffers for the next iteration
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
return new ComplexSamples(i, q, timestamp);
}
}

View File

@@ -1,141 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 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.buffer.sample;
import io.github.dsheirer.sample.complex.ComplexSamples;
import java.util.Arrays;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Vector SIMD implementation for non-packed native buffers
*/
public class SampleBufferIteratorVector64Bits extends SampleBufferIterator<ComplexSamples>
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_64;
/**
* Constructs an instance
*
* @param samples from the device, either packed or unpacked.
* @param residualI samples from last buffer
* @param residualQ samples from last buffer
* @param averageDc measured
* @param timestamp of the buffer
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public SampleBufferIteratorVector64Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
}
@Override
public ComplexSamples next()
{
if(mSamplesPointer >= mSamples.length)
{
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
int offset = mSamplesPointer;
int fragmentPointer = 0;
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
while(fragmentPointer < FRAGMENT_SIZE)
{
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
{
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
scaledSamples[y] = mSamples[offset++];
}
//SIMD scaling operation
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
.sub(2048.0f)
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
.sub(mAverageDc)
.intoArray(scaledSamples, 0);
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
{
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
}
}
mSamplesPointer = offset;
//The i line is a simple delay line, so we'll just copy those as a new array here.
float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE);
float[] q = new float[FRAGMENT_SIZE];
FloatVector accumulator;
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 2);
FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 4);
FloatVector f4 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 6);
FloatVector f5 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8);
FloatVector f6 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 10);
FloatVector f7 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 12);
FloatVector f8 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 14);
FloatVector f9 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16);
FloatVector f10 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 18);
FloatVector f11 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 20);
FloatVector f12 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 22);
for(int x = 0; x < FRAGMENT_SIZE; x++)
{
accumulator = FloatVector.zero(VECTOR_SPECIES);
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 2), accumulator);
accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator);
accumulator = f4.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 6), accumulator);
accumulator = f5.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
accumulator = f6.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 10), accumulator);
accumulator = f7.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator);
accumulator = f8.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 14), accumulator);
accumulator = f9.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator);
accumulator = f10.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 18), accumulator);
accumulator = f11.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator);
accumulator = f12.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 22), accumulator);
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
if(x % 2 == 0)
{
q[x] = accumulator.reduceLanes(VectorOperators.ADD);
}
else
{
i[x] = -i[x];
q[x] = -accumulator.reduceLanes(VectorOperators.ADD);
}
}
//Copy residual end samples to beginning of buffers for the next iteration
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
return new ComplexSamples(i, q, timestamp);
}
}

View File

@@ -1,96 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 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.buffer.sample;
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
/**
* Scalar implementation for non-packed native buffers
*/
public class SampleInterleavedBufferIteratorScalar extends SampleBufferIterator<InterleavedComplexSamples>
{
/**
* Constructs an instance
*
* @param samples from the device, either packed or unpacked.
* @param residualI samples from last buffer
* @param residualQ samples from last buffer
* @param averageDc measured
* @param timestamp of the buffer
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public SampleInterleavedBufferIteratorScalar(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
}
@Override
public InterleavedComplexSamples next()
{
if(mSamplesPointer >= mSamples.length)
{
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
int offset = mSamplesPointer;
for(int x = 0; x < FRAGMENT_SIZE; x++)
{
mIBuffer[x + I_OVERLAP] = scale(mSamples[offset++], mAverageDc);
mQBuffer[x + Q_OVERLAP] = scale(mSamples[offset++], mAverageDc);
}
mSamplesPointer = offset;
float[] samples = new float[FRAGMENT_SIZE * 2];
float accumulator;
for(int x = 0; x < FRAGMENT_SIZE; x++)
{
accumulator = 0;
for(int tap = 0; tap < COEFFICIENTS.length; tap++)
{
accumulator += COEFFICIENTS[tap] * mQBuffer[x + tap];
}
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
if(x % 2 == 0)
{
samples[2 * x] = mIBuffer[x];
samples[2 * x + 1] = accumulator;
}
else
{
samples[2 * x] = -mIBuffer[x];
samples[2 * x + 1] = -accumulator;
}
}
//Copy residual end samples to beginning of buffers for the next iteration
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
return new InterleavedComplexSamples(samples, timestamp);
}
}

View File

@@ -1,125 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 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.buffer.sample;
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Vector SIMD implementation for non-packed native buffers
*/
public class SampleInterleavedBufferIteratorVector128Bits extends SampleBufferIterator<InterleavedComplexSamples>
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_128;
/**
* Constructs an instance
*
* @param samples from the device, either packed or unpacked.
* @param residualI samples from last buffer
* @param residualQ samples from last buffer
* @param averageDc measured
* @param timestamp of the buffer
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public SampleInterleavedBufferIteratorVector128Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
}
@Override
public InterleavedComplexSamples next()
{
if(mSamplesPointer >= mSamples.length)
{
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
int offset = mSamplesPointer;
int fragmentPointer = 0;
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
while(fragmentPointer < FRAGMENT_SIZE)
{
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
{
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
scaledSamples[y] = mSamples[offset++];
}
//SIMD scaling operation
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
.sub(2048.0f)
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
.sub(mAverageDc)
.intoArray(scaledSamples, 0);
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
{
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
}
}
mSamplesPointer = offset;
float[] samples = new float[FRAGMENT_SIZE * 2];
FloatVector accumulator;
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 4);
FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8);
FloatVector f4 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 12);
FloatVector f5 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16);
FloatVector f6 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 20);
for(int x = 0; x < FRAGMENT_SIZE; x++)
{
accumulator = FloatVector.zero(VECTOR_SPECIES);
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator);
accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
accumulator = f4.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator);
accumulator = f5.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator);
accumulator = f6.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator);
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
if(x % 2 == 0)
{
samples[2 * x] = mIBuffer[x];
samples[2 * x + 1] = accumulator.reduceLanes(VectorOperators.ADD);
}
else
{
samples[2 * x] = -mIBuffer[x];
samples[2 * x + 1] = -accumulator.reduceLanes(VectorOperators.ADD);
}
}
//Copy residual end samples to beginning of buffers for the next iteration
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
return new InterleavedComplexSamples(samples, timestamp);
}
}

View File

@@ -1,119 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 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.buffer.sample;
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Vector SIMD implementation for non-packed native buffers
*/
public class SampleInterleavedBufferIteratorVector256Bits extends SampleBufferIterator<InterleavedComplexSamples>
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_256;
/**
* Constructs an instance
*
* @param samples from the device, either packed or unpacked.
* @param residualI samples from last buffer
* @param residualQ samples from last buffer
* @param averageDc measured
* @param timestamp of the buffer
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public SampleInterleavedBufferIteratorVector256Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
}
@Override
public InterleavedComplexSamples next()
{
if(mSamplesPointer >= mSamples.length)
{
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
int offset = mSamplesPointer;
int fragmentPointer = 0;
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
while(fragmentPointer < FRAGMENT_SIZE)
{
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
{
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
scaledSamples[y] = mSamples[offset++];
}
//SIMD scaling operation
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
.sub(2048.0f)
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
.sub(mAverageDc)
.intoArray(scaledSamples, 0);
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
{
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
}
}
mSamplesPointer = offset;
float[] samples = new float[FRAGMENT_SIZE * 2];
FloatVector accumulator;
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8);
FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16);
for(int x = 0; x < FRAGMENT_SIZE; x++)
{
accumulator = FloatVector.zero(VECTOR_SPECIES);
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator);
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
if(x % 2 == 0)
{
samples[2 * x] = mIBuffer[x];
samples[2 * x + 1] = accumulator.reduceLanes(VectorOperators.ADD);
}
else
{
samples[2 * x] = -mIBuffer[x];
samples[2 * x + 1] = -accumulator.reduceLanes(VectorOperators.ADD);
}
}
//Copy residual end samples to beginning of buffers for the next iteration
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
return new InterleavedComplexSamples(samples, timestamp);
}
}

View File

@@ -1,122 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 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.buffer.sample;
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Vector SIMD implementation for non-packed native buffers
*/
public class SampleInterleavedBufferIteratorVector512Bits extends SampleBufferIterator<InterleavedComplexSamples>
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_512;
private float[] mFilterPart2;
/**
* Constructs an instance
*
* @param samples from the device, either packed or unpacked.
* @param residualI samples from last buffer
* @param residualQ samples from last buffer
* @param averageDc measured
* @param timestamp of the buffer
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public SampleInterleavedBufferIteratorVector512Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
//Arrange the last 8 taps of the filter into the higher indices of an array to align for SIMD
mFilterPart2 = new float[16];
System.arraycopy(COEFFICIENTS, 16, mFilterPart2, 8, 8);
}
@Override
public InterleavedComplexSamples next()
{
if(mSamplesPointer >= mSamples.length)
{
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
int offset = mSamplesPointer;
int fragmentPointer = 0;
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
while(fragmentPointer < FRAGMENT_SIZE)
{
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
{
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
scaledSamples[y] = mSamples[offset++];
}
//SIMD scaling operation
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
.sub(2048.0f)
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
.sub(mAverageDc)
.intoArray(scaledSamples, 0);
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
{
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
}
}
mSamplesPointer = offset;
float[] samples = new float[FRAGMENT_SIZE * 2];
FloatVector accumulator;
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, mFilterPart2, 0);
for(int x = 0; x < FRAGMENT_SIZE; x++)
{
accumulator = FloatVector.zero(VECTOR_SPECIES);
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
if(x % 2 == 0)
{
samples[2 * x] = mIBuffer[x];
samples[2 * x + 1] = accumulator.reduceLanes(VectorOperators.ADD);
}
else
{
samples[2 * x] = -mIBuffer[x];
samples[2 * x + 1] = -accumulator.reduceLanes(VectorOperators.ADD);
}
}
//Copy residual end samples to beginning of buffers for the next iteration
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
return new InterleavedComplexSamples(samples, timestamp);
}
}

View File

@@ -1,137 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 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.buffer.sample;
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Vector SIMD implementation for non-packed native buffers
*/
public class SampleInterleavedBufferIteratorVector64Bits extends SampleBufferIterator<InterleavedComplexSamples>
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_64;
/**
* Constructs an instance
*
* @param samples from the device, either packed or unpacked.
* @param residualI samples from last buffer
* @param residualQ samples from last buffer
* @param averageDc measured
* @param timestamp of the buffer
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public SampleInterleavedBufferIteratorVector64Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
}
@Override
public InterleavedComplexSamples next()
{
if(mSamplesPointer >= mSamples.length)
{
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
int offset = mSamplesPointer;
int fragmentPointer = 0;
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
while(fragmentPointer < FRAGMENT_SIZE)
{
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
{
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
scaledSamples[y] = mSamples[offset++];
}
//SIMD scaling operation
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
.sub(2048.0f)
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
.sub(mAverageDc)
.intoArray(scaledSamples, 0);
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
{
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
}
}
mSamplesPointer = offset;
float[] samples = new float[FRAGMENT_SIZE * 2];
FloatVector accumulator;
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 2);
FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 4);
FloatVector f4 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 6);
FloatVector f5 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8);
FloatVector f6 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 10);
FloatVector f7 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 12);
FloatVector f8 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 14);
FloatVector f9 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16);
FloatVector f10 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 18);
FloatVector f11 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 20);
FloatVector f12 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 22);
for(int x = 0; x < FRAGMENT_SIZE; x++)
{
accumulator = FloatVector.zero(VECTOR_SPECIES);
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 2), accumulator);
accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator);
accumulator = f4.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 6), accumulator);
accumulator = f5.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
accumulator = f6.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 10), accumulator);
accumulator = f7.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator);
accumulator = f8.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 14), accumulator);
accumulator = f9.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator);
accumulator = f10.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 18), accumulator);
accumulator = f11.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator);
accumulator = f12.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 22), accumulator);
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
if(x % 2 == 0)
{
samples[2 * x] = mIBuffer[x];
samples[2 * x + 1] = accumulator.reduceLanes(VectorOperators.ADD);
}
else
{
samples[2 * x] = -mIBuffer[x];
samples[2 * x + 1] = -accumulator.reduceLanes(VectorOperators.ADD);
}
}
//Copy residual end samples to beginning of buffers for the next iteration
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
return new InterleavedComplexSamples(samples, timestamp);
}
}

View File

@@ -1,103 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 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.buffer.sample;
import io.github.dsheirer.buffer.AbstractNativeBuffer;
import io.github.dsheirer.sample.complex.ComplexSamples;
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
import io.github.dsheirer.vector.calibrate.Implementation;
import java.util.Iterator;
/**
* Native buffer scalar implementation for non-packed samples.
*/
public class SampleNativeBuffer extends AbstractNativeBuffer
{
private final short[] mSamples;
private final short[] mResidualI;
private final short[] mResidualQ;
private final float mAverageDc;
private final Implementation mInterleavedImplementation;
private final Implementation mNonInterleavedImplementation;
/**
* Constructs an instance
* @param samples (non-packed) from the device
* @param residualI samples from previous buffer
* @param residualQ samples from previous buffer
* @param averageDc measured
* @param timestamp of the buffer
* @param interleavedImplementation optimal, scalar vs vector SIMD
* @param nonInterleavedImplementation optimal, scalar vs vector SIMD
* @param samplesPerMillisecond used to calculate sub-buffer fragment timestamp offsets from the start of this buffer.
*/
public SampleNativeBuffer(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, Implementation interleavedImplementation,
Implementation nonInterleavedImplementation, float samplesPerMillisecond)
{
super(timestamp, samplesPerMillisecond);
//Ensure we're an even multiple of the fragment size. Typically, this will be 64k or 128k
if(samples.length % SampleBufferIterator.FRAGMENT_SIZE != 0)
{
throw new IllegalArgumentException("Samples short[] length [" + samples.length +
"] must be an even multiple of " + SampleBufferIterator.FRAGMENT_SIZE);
}
mSamples = samples;
mResidualI = residualI;
mResidualQ = residualQ;
mAverageDc = averageDc;
mInterleavedImplementation = interleavedImplementation;
mNonInterleavedImplementation = nonInterleavedImplementation;
}
@Override
public int sampleCount()
{
return mSamples.length / 2;
}
@Override
public Iterator<ComplexSamples> iterator()
{
return switch(mNonInterleavedImplementation)
{
case VECTOR_SIMD_512 -> new SampleBufferIteratorVector512Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
case VECTOR_SIMD_256-> new SampleBufferIteratorVector256Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
case VECTOR_SIMD_128-> new SampleBufferIteratorVector128Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
case VECTOR_SIMD_64 ->new SampleBufferIteratorVector64Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
default -> new SampleBufferIteratorScalar(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
};
}
@Override
public Iterator<InterleavedComplexSamples> iteratorInterleaved()
{
return switch(mInterleavedImplementation)
{
case VECTOR_SIMD_512 -> new SampleInterleavedBufferIteratorVector512Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
case VECTOR_SIMD_256-> new SampleInterleavedBufferIteratorVector256Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
case VECTOR_SIMD_128-> new SampleInterleavedBufferIteratorVector128Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
case VECTOR_SIMD_64 ->new SampleInterleavedBufferIteratorVector64Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
default -> new SampleInterleavedBufferIteratorScalar(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
};
}
}

View File

@@ -1,135 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 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.buffer.sample;
import io.github.dsheirer.buffer.AbstractNativeBufferFactory;
import io.github.dsheirer.buffer.INativeBuffer;
import io.github.dsheirer.vector.calibrate.CalibrationManager;
import io.github.dsheirer.vector.calibrate.CalibrationType;
import io.github.dsheirer.vector.calibrate.Implementation;
import java.nio.ByteBuffer;
import java.util.Arrays;
/**
* Implements a factory for creating SignedByteNativeBuffer instances
*/
public class SampleNativeBufferFactory extends AbstractNativeBufferFactory
{
private boolean mSamplePacking = false;
private short[] mResidualI = new short[SampleBufferIterator.I_OVERLAP];
private short[] mResidualQ = new short[SampleBufferIterator.Q_OVERLAP];
private ISampleConverter mConverter;
private Implementation mInterleavedIteratorImplementation;
private Implementation mNonInterleavedIteratorImplementation;
/**
* Constructs an instance
*/
public SampleNativeBufferFactory()
{
updateConverter();
mInterleavedIteratorImplementation = CalibrationManager.getInstance()
.getImplementation(CalibrationType.SAMPLE_UNPACKED_INTERLEAVED_ITERATOR);
mNonInterleavedIteratorImplementation = CalibrationManager.getInstance()
.getImplementation(CalibrationType.SAMPLE_UNPACKED_ITERATOR);
}
/**
* Sample packing places two 12-bit samples into 3 bytes when enabled or
* places two 12-bit samples into 4 bytes when disabled.
*
* @param enabled
*/
public void setSamplePacking(boolean enabled)
{
mSamplePacking = enabled;
updateConverter();
}
/**
* Creates an optimal sample converter instance.
*/
private void updateConverter()
{
if(mSamplePacking)
{
if(!(mConverter instanceof ScalarPackedSampleConverter))
{
mConverter = new ScalarPackedSampleConverter();
}
}
else
{
Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.SAMPLE_PACKED_CONVERTER);
if(implementation == Implementation.VECTOR_SIMD_PREFERRED)
{
if(!(mConverter instanceof VectorUnpackedSampleConverter))
{
mConverter = new VectorUnpackedSampleConverter();
}
}
else
{
if(!(mConverter instanceof ScalarUnpackedSampleConverter))
{
mConverter = new ScalarUnpackedSampleConverter();
}
}
}
}
@Override
public INativeBuffer getBuffer(ByteBuffer buffer, long timestamp)
{
short[] samples = mConverter.convert(buffer);
INativeBuffer nativeBuffer = new SampleNativeBuffer(samples,
Arrays.copyOf(mResidualI, mResidualI.length),
Arrays.copyOf(mResidualQ, mResidualQ.length), mConverter.getAverageDc(), timestamp,
mInterleavedIteratorImplementation, mNonInterleavedIteratorImplementation, getSamplesPerMillisecond());
extractResidual(samples);
return nativeBuffer;
}
/**
* Extracts the residual overlap samples needed for continuity in the Hilbert transform filter.
* @param samples to extract residual from
*/
private void extractResidual(short[] samples)
{
int offset = samples.length - (SampleBufferIterator.I_OVERLAP * 2);
for(int i = 0; i < SampleBufferIterator.I_OVERLAP; i++)
{
mResidualI[i] = samples[offset + (2 * i)];
}
offset = samples.length - (SampleBufferIterator.Q_OVERLAP * 2) + 1;
for(int q = 0; q < SampleBufferIterator.Q_OVERLAP; q++)
{
mResidualQ[q] = samples[offset + (2 * q)];
}
}
}

View File

@@ -1,120 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 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.buffer.sample;
import io.github.dsheirer.buffer.DcCorrectionManager;
import java.nio.ByteBuffer;
/**
* Scalar implementation of sample converter for un-packed samples.
*/
public class ScalarPackedSampleConverter implements ISampleConverter
{
/**
* Manages DC calculations and processing interval
*/
private DcCorrectionManager mDcCalculationManager = new DcCorrectionManager();
@Override
public short[] convert(ByteBuffer buffer)
{
boolean shouldCalculateDc = mDcCalculationManager.shouldCalculateDc();
int offset = 0;
short sample;
short[] samples;
byte b1, b2, b3;
samples = new short[buffer.capacity() / 3 * 2];
if(shouldCalculateDc)
{
long dcAccumulator = 0;
for(int x = 0; x < samples.length; x += 2)
{
b1 = buffer.get(offset++);
b2 = buffer.get(offset++);
b3 = buffer.get(offset++);
sample = (short)(((b1 & 0xFF) << 4) | ((b2 & 0xF0) >> 4));
samples[x] = sample;
dcAccumulator += sample;
sample = (short)(((b2 & 0xF) << 8) | (b3 & 0xFF));
samples[x] = sample;
dcAccumulator += sample;
}
float averageDcNow = ((float)dcAccumulator / (float)samples.length) - 2048.0f;
averageDcNow *= SampleBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT;
mDcCalculationManager.adjust(averageDcNow);
}
else
{
for(int x = 0; x < samples.length; x += 2)
{
b1 = buffer.get(offset++);
b2 = buffer.get(offset++);
b3 = buffer.get(offset++);
sample = (short)(((b1 & 0xFF) << 4) | ((b2 & 0xF0) >> 4));
samples[x] = sample;
sample = (short)(((b2 & 0xF) << 8) | (b3 & 0xFF));
samples[x] = sample;
}
}
return samples;
}
@Override
public float getAverageDc()
{
return mDcCalculationManager.getAverageDc();
}
public static void main(String[] args)
{
byte[] raw = new byte[262144];
ByteBuffer buffer = ByteBuffer.wrap(raw);
ScalarPackedSampleConverter scalar = new ScalarPackedSampleConverter();
VectorUnpackedSampleConverter vector = new VectorUnpackedSampleConverter();
long accumulator = 0;
int iterations = 1_000_000;
System.out.println("Starting ...");
long start = System.currentTimeMillis();
for(int count = 0; count < iterations; count++)
{
// short[] samples = scalar.convert(buffer);
short[] samples = vector.convert(buffer);
accumulator += samples[1];
}
long elapsed = System.currentTimeMillis() - start;
System.out.println("Accumulator: " + accumulator);
System.out.println("Elapsed: " + (elapsed / 1000.0f));
System.out.println("Finished");
}
}

View File

@@ -1,83 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 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.buffer.sample;
import io.github.dsheirer.buffer.DcCorrectionManager;
import java.nio.ByteBuffer;
/**
* Scalar implementation of sample converter for un-packed samples.
*/
public class ScalarUnpackedSampleConverter implements ISampleConverter
{
/**
* Manages DC calculations and processing interval
*/
private DcCorrectionManager mDcCalculationManager = new DcCorrectionManager();
@Override
public short[] convert(ByteBuffer buffer)
{
boolean shouldCalculateDc = mDcCalculationManager.shouldCalculateDc();
int offset = 0;
short sample;
short[] samples;
byte b1, b2;
samples = new short[buffer.capacity() / 2];
if(shouldCalculateDc)
{
long dcAccumulator = 0;
for(int x = 0; x < samples.length; x++)
{
b1 = buffer.get(offset++);
b2 = buffer.get(offset++);
sample = (short)(((b2 & 0x0F) << 8) | (b1 & 0xFF));
samples[x] = sample;
dcAccumulator += sample;
}
float averageDcNow = ((float)dcAccumulator / (float)samples.length) - 2048.0f;
averageDcNow *= SampleBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT;
mDcCalculationManager.adjust(averageDcNow);
}
else
{
for(int x = 0; x < samples.length; x++)
{
b1 = buffer.get(offset++);
b2 = buffer.get(offset++);
sample = (short)(((b2 & 0x0F) << 8) | (b1 & 0xFF));
samples[x] = sample;
}
}
return samples;
}
@Override
public float getAverageDc()
{
return mDcCalculationManager.getAverageDc();
}
}

View File

@@ -1,129 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 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.buffer.sample;
import io.github.dsheirer.buffer.DcCorrectionManager;
import java.nio.ByteBuffer;
import jdk.incubator.vector.ShortVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Scalar implementation of sample converter for un-packed samples.
*/
public class VectorUnpackedSampleConverter implements ISampleConverter
{
private static final VectorSpecies<Short> VECTOR_SPECIES = ShortVector.SPECIES_PREFERRED;
/**
* Manages DC calculations and processing interval
*/
private DcCorrectionManager mDcCalculationManager = new DcCorrectionManager();
@Override
public short[] convert(ByteBuffer buffer)
{
boolean shouldCalculateDc = mDcCalculationManager.shouldCalculateDc();
short sample;
short[] samples;
byte b1, b2;
samples = new short[buffer.capacity() / 2];
int bytesOffset;
int rawPointer = 0;
ShortVector vector;
int samplesOffset = 0;
short[] bytes1 = new short[VECTOR_SPECIES.length()];
short[] bytes2 = new short[VECTOR_SPECIES.length()];
if(shouldCalculateDc)
{
long dcAccumulator = 0;
for(; samplesOffset < VECTOR_SPECIES.loopBound(samples.length); samplesOffset += VECTOR_SPECIES.length())
{
bytesOffset = 0;
while(bytesOffset < VECTOR_SPECIES.length())
{
bytes1[bytesOffset] = (short)(buffer.get(rawPointer++) & 0xFF);
bytes2[bytesOffset++] = (short)(buffer.get(rawPointer++) & 0x0F);
}
vector = ShortVector.fromArray(VECTOR_SPECIES, bytes1, 0)
.or(ShortVector.fromArray(VECTOR_SPECIES, bytes2, 0).lanewise(VectorOperators.LSHL, 8));
dcAccumulator += vector.reduceLanes(VectorOperators.ADD);
vector.intoArray(samples, samplesOffset);
}
for(; samplesOffset < samples.length; samplesOffset++)
{
b1 = buffer.get(rawPointer++);
b2 = buffer.get(rawPointer++);
sample = (short)(((b2 & 0x0F) << 8) | (b1 & 0xFF));
samples[samplesOffset] = sample;
dcAccumulator += sample;
}
float averageDcNow = ((float)dcAccumulator / (float)samples.length) - 2048.0f;
averageDcNow *= SampleBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT;
mDcCalculationManager.adjust(averageDcNow);
}
else
{
for(; samplesOffset < VECTOR_SPECIES.loopBound(samples.length); samplesOffset += VECTOR_SPECIES.length())
{
bytesOffset = 0;
while(bytesOffset < VECTOR_SPECIES.length())
{
bytes1[bytesOffset] = (short)(buffer.get(rawPointer++) & 0xFF);
bytes2[bytesOffset++] = (short)(buffer.get(rawPointer++) & 0x0F);
}
vector = ShortVector.fromArray(VECTOR_SPECIES, bytes1, 0)
.or(ShortVector.fromArray(VECTOR_SPECIES, bytes2, 0).lanewise(VectorOperators.LSHL, 8));
vector.intoArray(samples, samplesOffset);
}
for(; samplesOffset < samples.length; samplesOffset++)
{
b1 = buffer.get(rawPointer++);
b2 = buffer.get(rawPointer++);
sample = (short)(((b2 & 0x0F) << 8) | (b1 & 0xFF));
samples[samplesOffset] = sample;
}
}
return samples;
}
@Override
public float getAverageDc()
{
return mDcCalculationManager.getAverageDc();
}
}

View File

@@ -1,157 +1,102 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 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.source.tuner.airspy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import io.github.dsheirer.airspy.PartIdSerialNumber;
/**
* Airspy device information, read from the device by libairspy.
*/
public class AirspyDeviceInformation
{
private final static Logger mLog =
LoggerFactory.getLogger( AirspyDeviceInformation.class );
private AirspyTunerController.BoardID mBoardID = AirspyTunerController.BoardID.UNKNOWN;
private String mFirmwareVersion = "Unknown";
private String mPartNumber = "Unknown";
private String mSerialNumber = "Unknown";
private final String mBoardName;
private final String mFirmwareVersion;
private final PartIdSerialNumber mPartIdSerialNumber;
/**
* Airspy Device Information - provides parsing and access to device
* information components.
* Constructs an instance
* @param boardName from the device
* @param firmwareVersion string from the device
* @param partIdSerialNumber from the device
*/
public AirspyDeviceInformation()
public AirspyDeviceInformation(String boardName, String firmwareVersion, PartIdSerialNumber partIdSerialNumber)
{
mBoardName = boardName;
mFirmwareVersion = firmwareVersion;
mPartIdSerialNumber = partIdSerialNumber;
}
public void setBoardID( int boardID )
public String getBoardName()
{
mBoardID = AirspyTunerController.BoardID.fromValue( boardID );
return mBoardName;
}
public AirspyTunerController.BoardID getBoardID()
{
return mBoardID;
}
public void setVersion( byte[] data )
{
if( data != null && data.length > 0 )
{
/* Truncate the version string if we have a line break - ASCII 10 */
for( int x = 0; x < data.length; x++ )
{
if( data[ x ] == (byte)10 )
{
mFirmwareVersion = new String( Arrays.copyOf( data, x ) );
mFirmwareVersion = mFirmwareVersion.replace( "AirSpy NOS ", "" );
return;
}
}
mFirmwareVersion = new String( data );
mFirmwareVersion = mFirmwareVersion.replace( "AirSpy NOS ", "" );
}
else
{
mLog.error( "Error setting airspy version byte data - null:" +
( data == null ? " TRUE" : " FALSE length:" + data.length ) );
}
}
/**
* Firmware version, without the "AirSpy NOS " prefix and truncated at the first line break.
*/
public String getVersion()
{
return mFirmwareVersion;
}
public void setPartAndSerialNumber( byte[] data )
{
if( data != null && data.length == 24 )
if(mFirmwareVersion == null)
{
//Note: values are byte-reversed (big-endian) 32-bit chunks
StringBuilder part = new StringBuilder();
part.append( format( data[ 3 ] ) );
part.append( format( data[ 2 ] ) );
part.append( format( data[ 1 ] ) );
part.append( format( data[ 0 ] ) );
part.append( format( data[ 7 ] ) );
part.append( format( data[ 6 ] ) );
part.append( format( data[ 5 ] ) );
part.append( format( data[ 4 ] ) );
mPartNumber = part.toString();
StringBuilder serial = new StringBuilder();
//Note: current airspy library only exposes 64-bits of serial number
//address space, but appears to have room for 128-bits. We won't
//add the first 64 bits if it's zeros
if( !( data[ 8 ] == 0 && data[ 9 ] == 0 && data[ 10 ] == 0 && data[ 11 ] == 0 ) )
{
serial.append( format( data[ 11 ] ) );
serial.append( format( data[ 10 ] ) );
serial.append( format( data[ 9 ] ) );
serial.append( format( data[ 8 ] ) );
serial.append( "-" );
}
if( !( data[ 12 ] == 0 && data[ 13 ] == 0 && data[ 14 ] == 0 && data[ 15 ] == 0 ) )
{
serial.append( format( data[ 15 ] ) );
serial.append( format( data[ 14 ] ) );
serial.append( format( data[ 13 ] ) );
serial.append( format( data[ 12 ] ) );
serial.append( "-" );
}
serial.append( format( data[ 19 ] ) );
serial.append( format( data[ 18 ] ) );
serial.append( format( data[ 17 ] ) );
serial.append( format( data[ 16 ] ) );
serial.append( "-" );
serial.append( format( data[ 23 ] ) );
serial.append( format( data[ 22 ] ) );
serial.append( format( data[ 21 ] ) );
serial.append( format( data[ 20 ] ) );
mSerialNumber = serial.toString();
return "Unknown";
}
else
String version = mFirmwareVersion;
int lineBreak = version.indexOf('\n');
if(lineBreak >= 0)
{
mLog.error( "Error setting airspy part and serial byte data - null:" +
( data == null ? " TRUE" : " FALSE length:" + data.length ) );
version = version.substring(0, lineBreak);
}
return version.replace("AirSpy NOS ", "").trim();
}
private String format( byte value )
{
return String.format( "%02X", value );
}
/**
* Serial number, formatted as the identifier that tuner configurations are saved under.
*/
public String getSerialNumber()
{
return mSerialNumber;
long serial = mPartIdSerialNumber.serialNumber();
return String.format("%08X-%08X", serial >>> 32, serial & 0xFFFFFFFFL);
}
public String getPartNumber()
{
return mPartNumber;
return String.format("%08X%08X", mPartIdSerialNumber.partId1(), mPartIdSerialNumber.partId2());
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( "Airspy Device Information\n" );
sb.append( "Board: " );
sb.append( getBoardID().getLabel() );
sb.append( "\nPart Number: " );
sb.append( getPartNumber() );
sb.append( "\nSerial Number: " );
sb.append( getSerialNumber() );
sb.append( "\nFirmware: " );
sb.append( getVersion() );
sb.append("Airspy Device Information\n");
sb.append("Board: ");
sb.append(getBoardName());
sb.append("\nPart Number: ");
sb.append(getPartNumber());
sb.append("\nSerial Number: ");
sb.append(getSerialNumber());
sb.append("\nFirmware: ");
sb.append(getVersion());
return sb.toString();
}
}

View File

@@ -22,7 +22,6 @@ import io.github.dsheirer.preference.source.ChannelizerType;
import io.github.dsheirer.source.tuner.ITunerErrorListener;
import io.github.dsheirer.source.tuner.Tuner;
import io.github.dsheirer.source.tuner.TunerClass;
import io.github.dsheirer.source.tuner.airspy.AirspyTunerController.BoardID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -71,7 +70,7 @@ public class AirspyTuner extends Tuner
mLog.error("error getting serial number", e);
}
return BoardID.AIRSPY.getLabel();
return "Airspy";
}
@Override

View File

@@ -689,8 +689,8 @@ public class AirspyTunerEditor extends TunerEditor<AirspyTuner, AirspyTunerConfi
sb.append(info.getPartNumber());
sb.append("<br>");
sb.append("<b>Board ID: </b>");
sb.append(info.getBoardID().getLabel());
sb.append("<b>Board: </b>");
sb.append(info.getBoardName());
sb.append("<br>");
return sb.toString();

View File

@@ -39,9 +39,6 @@ import io.github.dsheirer.vector.calibrate.magnitude.MagnitudeCalibration;
import io.github.dsheirer.vector.calibrate.mixer.ComplexMixerCalibration;
import io.github.dsheirer.vector.calibrate.oscillator.ComplexOscillatorCalibration;
import io.github.dsheirer.vector.calibrate.oscillator.RealOscillatorCalibration;
import io.github.dsheirer.vector.calibrate.sample.PackedSampleConverterCalibration;
import io.github.dsheirer.vector.calibrate.sample.UnpackedInterleavedSampleConverterCalibration;
import io.github.dsheirer.vector.calibrate.sample.UnpackedSampleConverterCalibration;
import io.github.dsheirer.vector.calibrate.sync.DMRSoftSyncCalibration;
import io.github.dsheirer.vector.calibrate.sync.NXDNSoftSyncCalibration;
import io.github.dsheirer.vector.calibrate.sync.P25P1SoftSyncCalibration;
@@ -103,9 +100,6 @@ public class CalibrationManager
sInstance = new CalibrationManager();
sInstance.add(new PackedSampleConverterCalibration());
sInstance.add(new UnpackedSampleConverterCalibration());
sInstance.add(new UnpackedInterleavedSampleConverterCalibration());
sInstance.add(new AmDemodulatorCalibration());
sInstance.add(new ChannelDFTCalibration());
sInstance.add(new ComplexGainCalibration());

View File

@@ -53,9 +53,6 @@ public enum CalibrationType
OSCILLATOR_REAL("Real Oscillator", 1),
P25P1_SOFT_SYNC_DETECTOR("P25 Phase 1 Soft Sync Detector", 2),
P25P2_SOFT_SYNC_DETECTOR("P25 Phase 2 Soft Sync Detector", 1),
SAMPLE_PACKED_CONVERTER("Packed Sample Converter", 2),
SAMPLE_UNPACKED_INTERLEAVED_ITERATOR("Unpacked Interleaved Sample Iterator", 2),
SAMPLE_UNPACKED_ITERATOR("Unpacked Sample Iterator", 2),
SQUELCHING_FM_DEMODULATOR("Squelching FM Demodulator", 1),
WINDOW("Window", 1);

View File

@@ -1,137 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 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.vector.calibrate.sample;
import io.github.dsheirer.buffer.sample.ScalarUnpackedSampleConverter;
import io.github.dsheirer.buffer.sample.VectorUnpackedSampleConverter;
import io.github.dsheirer.vector.calibrate.Calibration;
import io.github.dsheirer.vector.calibrate.CalibrationException;
import io.github.dsheirer.vector.calibrate.CalibrationType;
import io.github.dsheirer.vector.calibrate.Implementation;
import java.nio.ByteBuffer;
import org.apache.commons.math3.stat.descriptive.moment.Mean;
public class PackedSampleConverterCalibration extends Calibration
{
private static final int BUFFER_SIZE = 262144;
private static final int ITERATION_DURATION_MS = 1000;
private static final int WARM_UP_ITERATIONS = 5;
private static final int TEST_ITERATIONS = 5;
/**
* Constructs an instance
*/
public PackedSampleConverterCalibration()
{
super(CalibrationType.SAMPLE_PACKED_CONVERTER);
}
@Override
public void calibrate() throws CalibrationException
{
Mean scalarMean = new Mean();
Mean vectorMean = new Mean();
byte[] samples = new byte[BUFFER_SIZE];
ByteBuffer buffer = ByteBuffer.wrap(samples);
for(int scalarTest = 0; scalarTest < WARM_UP_ITERATIONS; scalarTest++)
{
long score = testScalar(buffer);
scalarMean.increment(score);
}
mLog.info("PACKED SAMPLE CONVERTER WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
for(int vectorTest = 0; vectorTest < WARM_UP_ITERATIONS; vectorTest++)
{
long score = testVector(buffer);
vectorMean.increment(score);
}
mLog.info("PACKED SAMPLE CONVERTER WARMUP - VECTOR: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
scalarMean.clear();
vectorMean.clear();
for(int scalarTest = 0; scalarTest < TEST_ITERATIONS; scalarTest++)
{
long score = testScalar(buffer);
scalarMean.increment(score);
}
mLog.info("PACKED SAMPLE CONVERTER - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
for(int vectorTest = 0; vectorTest < TEST_ITERATIONS; vectorTest++)
{
long score = testVector(buffer);
vectorMean.increment(score);
}
mLog.info("PACKED SAMPLE CONVERTER - VECTOR: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
if(scalarMean.getResult() > vectorMean.getResult())
{
setImplementation(Implementation.SCALAR);
}
else
{
setImplementation(Implementation.VECTOR_SIMD_PREFERRED);
}
mLog.info("PACKED SAMPLE CONVERTER - SETTING OPTIMAL IMPLEMENTATION TO: " + getImplementation());
}
private long testScalar(ByteBuffer buffer)
{
long start = System.currentTimeMillis();
ScalarUnpackedSampleConverter scalor = new ScalarUnpackedSampleConverter();
long count = 0;
double accumulator = 0.0;
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
short[] converted = scalor.convert(buffer);
accumulator += converted[2];
count++;
}
return count + (long)(accumulator * 0);
}
private long testVector(ByteBuffer buffer)
{
VectorUnpackedSampleConverter vector = new VectorUnpackedSampleConverter();
long start = System.currentTimeMillis();
double accumulator = 0.0;
long count = 0;
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
short[] converted = vector.convert(buffer);
accumulator += converted[2];
count++;
}
return count + (long)(accumulator * 0);
}
}

View File

@@ -1,324 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 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.vector.calibrate.sample;
import io.github.dsheirer.buffer.sample.SampleBufferIterator;
import io.github.dsheirer.buffer.sample.SampleInterleavedBufferIteratorScalar;
import io.github.dsheirer.buffer.sample.SampleInterleavedBufferIteratorVector128Bits;
import io.github.dsheirer.buffer.sample.SampleInterleavedBufferIteratorVector256Bits;
import io.github.dsheirer.buffer.sample.SampleInterleavedBufferIteratorVector512Bits;
import io.github.dsheirer.buffer.sample.SampleInterleavedBufferIteratorVector64Bits;
import io.github.dsheirer.vector.calibrate.Calibration;
import io.github.dsheirer.vector.calibrate.CalibrationException;
import io.github.dsheirer.vector.calibrate.CalibrationType;
import io.github.dsheirer.vector.calibrate.Implementation;
import jdk.incubator.vector.FloatVector;
import org.apache.commons.math3.stat.descriptive.moment.Mean;
/**
* Calculates optimal implementation (SCALAR vs VECTOR) for interleaved sample native buffers.
*/
public class UnpackedInterleavedSampleConverterCalibration extends Calibration
{
private static final int BUFFER_SIZE = 131072;
private static final int ITERATION_DURATION_MS = 1000;
private static final int WARMUP_ITERATIONS = 5;
private static final int TEST_ITERATIONS = 5;
/**
* Constructs an instance
*/
public UnpackedInterleavedSampleConverterCalibration()
{
super(CalibrationType.SAMPLE_UNPACKED_INTERLEAVED_ITERATOR);
}
@Override
public void calibrate() throws CalibrationException
{
short[] samples = getShortSamples(BUFFER_SIZE);
short[] residualI = getShortSamples(SampleBufferIterator.I_OVERLAP);
short[] residualQ = getShortSamples(SampleBufferIterator.Q_OVERLAP);
//Warm-Up Phase ....
mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER - VECTOR SIMD LANES PREFERRED: " + FloatVector.SPECIES_PREFERRED.length());
Mean scalarMean = new Mean();
for(int x = 0; x < WARMUP_ITERATIONS; x++)
{
long score = calibrateScalar(samples, residualI, residualQ);
scalarMean.increment(score);
}
mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
switch(FloatVector.SPECIES_PREFERRED.length())
{
//Deliberate fall-through of each case statement so that we can test the largest
//SIMD lane width supported by hardware down to the smallest SIMD lane width.
case 16:
{
Mean vectorMean = new Mean();
for(int x = 0; x < WARMUP_ITERATIONS; x++)
{
long score = calibrateVector512(samples, residualI, residualQ);
vectorMean.increment(score);
}
mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
}
case 8:
{
Mean vectorMean = new Mean();
for(int x = 0; x < WARMUP_ITERATIONS; x++)
{
long score = calibrateVector256(samples, residualI, residualQ);
vectorMean.increment(score);
}
mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
}
case 4:
{
Mean vectorMean = new Mean();
for(int x = 0; x < WARMUP_ITERATIONS; x++)
{
long score = calibrateVector128(samples, residualI, residualQ);
vectorMean.increment(score);
}
mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
}
case 2:
{
Mean vectorMean = new Mean();
for(int x = 0; x < WARMUP_ITERATIONS; x++)
{
long score = calibrateVector64(samples, residualI, residualQ);
vectorMean.increment(score);
}
mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
}
//Test Phase ....
scalarMean.clear();
for(int x = 0; x < TEST_ITERATIONS; x++)
{
long score = calibrateScalar(samples, residualI, residualQ);
scalarMean.increment(score);
}
double bestScore = scalarMean.getResult();
setImplementation(Implementation.SCALAR);
mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
switch(FloatVector.SPECIES_PREFERRED.length())
{
//Deliberate fall-through of each case statement so that we can test the largest
//SIMD lane width supported by hardware down to the smallest SIMD lane width.
case 16:
{
Mean vectorMean = new Mean();
for(int x = 0; x < TEST_ITERATIONS; x++)
{
long score = calibrateVector512(samples, residualI, residualQ);
vectorMean.increment(score);
}
if(vectorMean.getResult() > bestScore)
{
bestScore = vectorMean.getResult();
setImplementation(Implementation.VECTOR_SIMD_512);
}
mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER - VECTOR 512: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
}
case 8:
{
Mean vectorMean = new Mean();
for(int x = 0; x < TEST_ITERATIONS; x++)
{
long score = calibrateVector256(samples, residualI, residualQ);
vectorMean.increment(score);
}
if(vectorMean.getResult() > bestScore)
{
bestScore = vectorMean.getResult();
setImplementation(Implementation.VECTOR_SIMD_256);
}
mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER - VECTOR 256: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
}
case 4:
{
Mean vectorMean = new Mean();
for(int x = 0; x < TEST_ITERATIONS; x++)
{
long score = calibrateVector128(samples, residualI, residualQ);
vectorMean.increment(score);
}
if(vectorMean.getResult() > bestScore)
{
bestScore = vectorMean.getResult();
setImplementation(Implementation.VECTOR_SIMD_128);
}
mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER - VECTOR 128: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
}
case 2:
{
Mean vectorMean = new Mean();
for(int x = 0; x < TEST_ITERATIONS; x++)
{
long score = calibrateVector64(samples, residualI, residualQ);
vectorMean.increment(score);
}
if(vectorMean.getResult() > bestScore)
{
setImplementation(Implementation.VECTOR_SIMD_64);
}
mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER - VECTOR 64: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
}
}
}
mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation());
}
private long calibrateScalar(short[] samples, short[] residualI, short[] residualQ)
{
long start = System.currentTimeMillis();
double accumulator = 0.0;
long count = 0;
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
SampleInterleavedBufferIteratorScalar iterator = new SampleInterleavedBufferIteratorScalar(samples, residualI,
residualQ, 0.0f, System.currentTimeMillis(), 0.0f);
while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS))
{
accumulator += iterator.next().samples()[2];
count++;
}
}
return count + (long)(accumulator * 0);
}
private long calibrateVector64(short[] samples, short[] residualI, short[] residualQ)
{
long start = System.currentTimeMillis();
double accumulator = 0.0;
long count = 0;
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
SampleInterleavedBufferIteratorVector64Bits iterator = new SampleInterleavedBufferIteratorVector64Bits(samples, residualI,
residualQ, 0.0f, System.currentTimeMillis(), 0.0f);
while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS))
{
accumulator += iterator.next().samples()[2];
count++;
}
}
return count + (long)(accumulator * 0);
}
private long calibrateVector128(short[] samples, short[] residualI, short[] residualQ)
{
long start = System.currentTimeMillis();
double accumulator = 0.0;
long count = 0;
for(int x = 0; x < ITERATION_DURATION_MS; x++)
{
SampleInterleavedBufferIteratorVector128Bits iterator = new SampleInterleavedBufferIteratorVector128Bits(samples, residualI,
residualQ, 0.0f, System.currentTimeMillis(), 0.0f);
while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS))
{
accumulator += iterator.next().samples()[2];
count++;
}
}
return count + (long)(accumulator * 0);
}
private long calibrateVector256(short[] samples, short[] residualI, short[] residualQ)
{
long start = System.currentTimeMillis();
double accumulator = 0.0;
long count = 0;
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
SampleInterleavedBufferIteratorVector256Bits iterator =
new SampleInterleavedBufferIteratorVector256Bits(samples, residualI, residualQ, 0.0f, System.currentTimeMillis(), 0.0f);
while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS))
{
accumulator += iterator.next().samples()[2];
count++;
}
}
return count + (long)(accumulator * 0);
}
private long calibrateVector512(short[] samples, short[] residualI, short[] residualQ)
{
long start = System.currentTimeMillis();
double accumulator = 0.0;
long count = 0;
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
SampleInterleavedBufferIteratorVector512Bits iterator =
new SampleInterleavedBufferIteratorVector512Bits(samples, residualI, residualQ, 0.0f,
System.currentTimeMillis(), 0.0f);
while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS))
{
accumulator += iterator.next().samples()[2];
count++;
}
}
return count + (long)(accumulator * 0);
}
}

View File

@@ -1,313 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 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.vector.calibrate.sample;
import io.github.dsheirer.buffer.sample.SampleBufferIterator;
import io.github.dsheirer.buffer.sample.SampleBufferIteratorScalar;
import io.github.dsheirer.buffer.sample.SampleBufferIteratorVector128Bits;
import io.github.dsheirer.buffer.sample.SampleBufferIteratorVector256Bits;
import io.github.dsheirer.buffer.sample.SampleBufferIteratorVector512Bits;
import io.github.dsheirer.buffer.sample.SampleBufferIteratorVector64Bits;
import io.github.dsheirer.vector.calibrate.Calibration;
import io.github.dsheirer.vector.calibrate.CalibrationException;
import io.github.dsheirer.vector.calibrate.CalibrationType;
import io.github.dsheirer.vector.calibrate.Implementation;
import jdk.incubator.vector.FloatVector;
import org.apache.commons.math3.stat.descriptive.moment.Mean;
/**
* Calculates optimal implementation (SCALAR vs VECTOR) for non-interleaved sample native buffers.
*/
public class UnpackedSampleConverterCalibration extends Calibration
{
private static final int BUFFER_SIZE = 131072;
private static final int ITERATION_DURATION_MS = 1000;
private static final int WARMUP_ITERATIONS = 5;
private static final int TEST_ITERATIONS = 5;
/**
* Constructs an instance
*/
public UnpackedSampleConverterCalibration()
{
super(CalibrationType.SAMPLE_UNPACKED_ITERATOR);
}
@Override
public void calibrate() throws CalibrationException
{
short[] samples = getShortSamples(BUFFER_SIZE);
short[] residualI = getShortSamples(SampleBufferIterator.I_OVERLAP);
short[] residualQ = getShortSamples(SampleBufferIterator.Q_OVERLAP);
mLog.info("UNPACKED SAMPLE CONVERTER - VECTOR SIMD LANES PREFERRED: " + FloatVector.SPECIES_PREFERRED.length());
//Warm-Up Phase ....
Mean scalarMean = new Mean();
for(int warmup = 0; warmup < WARMUP_ITERATIONS; warmup++)
{
long score = testScalar(samples, residualI, residualQ);
scalarMean.increment(score);
}
mLog.info("UNPACKED SAMPLE CONVERTER WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
switch(FloatVector.SPECIES_PREFERRED.length())
{
//Deliberate fall-through of each case statement so that we can test the largest
//SIMD lane width supported by hardware down to the smallest SIMD lane width.
case 16:
{
Mean vector512Mean = new Mean();
for(int warmup = 0; warmup < WARMUP_ITERATIONS; warmup++)
{
long score = testVector512(samples, residualI, residualQ);
vector512Mean.increment(score);
}
mLog.info("UNPACKED SAMPLE CONVERTER WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult()));
}
case 8:
{
Mean vector256Mean = new Mean();
for(int warmup = 0; warmup < WARMUP_ITERATIONS; warmup++)
{
long score = testVector256(samples, residualI, residualQ);
vector256Mean.increment(score);
}
mLog.info("UNPACKED SAMPLE CONVERTER WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult()));
}
case 4:
{
Mean vector128Mean = new Mean();
for(int warmup = 0; warmup < WARMUP_ITERATIONS; warmup++)
{
long score = testVector128(samples, residualI, residualQ);
vector128Mean.increment(score);
}
mLog.info("UNPACKED SAMPLE CONVERTER WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult()));
}
case 2:
{
Mean vector64Mean = new Mean();
for(int warmup = 0; warmup < WARMUP_ITERATIONS; warmup++)
{
long score = testVector64(samples, residualI, residualQ);
vector64Mean.increment(score);
}
mLog.info("UNPACKED SAMPLE CONVERTER WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult()));
}
}
//Test Phase ....
scalarMean.clear();
for(int test = 0; test < TEST_ITERATIONS; test++)
{
long score = testScalar(samples, residualI, residualQ);
scalarMean.increment(score);
}
double bestScore = scalarMean.getResult();
setImplementation(Implementation.SCALAR);
mLog.info("UNPACKED SAMPLE CONVERTER - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
switch(FloatVector.SPECIES_PREFERRED.length())
{
//Deliberate fall-through of each case statement so that we can test the largest
//SIMD lane width supported by hardware down to the smallest SIMD lane width.
case 16:
{
Mean vector512Mean = new Mean();
for(int test = 0; test < TEST_ITERATIONS; test++)
{
long score = testVector512(samples, residualI, residualQ);
vector512Mean.increment(score);
}
mLog.info("UNPACKED SAMPLE CONVERTER - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult()));
if(vector512Mean.getResult() > bestScore)
{
bestScore = vector512Mean.getResult();
setImplementation(Implementation.VECTOR_SIMD_512);
}
}
case 8:
{
Mean vector256Mean = new Mean();
for(int test = 0; test < TEST_ITERATIONS; test++)
{
long score = testVector256(samples, residualI, residualQ);
vector256Mean.increment(score);
}
mLog.info("UNPACKED SAMPLE CONVERTER - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult()));
if(vector256Mean.getResult() > bestScore)
{
bestScore = vector256Mean.getResult();
setImplementation(Implementation.VECTOR_SIMD_256);
}
}
case 4:
{
Mean vector128Mean = new Mean();
for(int test = 0; test < TEST_ITERATIONS; test++)
{
long score = testVector128(samples, residualI, residualQ);
vector128Mean.increment(score);
}
mLog.info("UNPACKED SAMPLE CONVERTER - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult()));
if(vector128Mean.getResult() > bestScore)
{
bestScore = vector128Mean.getResult();
setImplementation(Implementation.VECTOR_SIMD_128);
}
}
case 2:
{
Mean vector64Mean = new Mean();
for(int test = 0; test < TEST_ITERATIONS; test++)
{
long score = testVector64(samples, residualI, residualQ);
vector64Mean.increment(score);
}
mLog.info("UNPACKED SAMPLE CONVERTER - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult()));
if(vector64Mean.getResult() > bestScore)
{
setImplementation(Implementation.VECTOR_SIMD_64);
}
}
}
mLog.info("UNPACKED SAMPLE CONVERTER - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation());
}
private long testScalar(short[] samples, short[] residualI, short[] residualQ)
{
long start = System.currentTimeMillis();
double accumulator = 0.0;
long count = 0;
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
SampleBufferIteratorScalar iterator = new SampleBufferIteratorScalar(samples, residualI,
residualQ, 0.0f, System.currentTimeMillis(), 0.0f);
while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS))
{
accumulator += iterator.next().i()[2];
count++;
}
}
return count + (long)(accumulator * 0);
}
private long testVector64(short[] samples, short[] residualI, short[] residualQ)
{
long start = System.currentTimeMillis();
double accumulator = 0.0;
long count = 0;
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
SampleBufferIteratorVector64Bits iterator = new SampleBufferIteratorVector64Bits(samples, residualI,
residualQ, 0.0f, System.currentTimeMillis(), 0.0f);
while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS))
{
accumulator += iterator.next().i()[2];
count++;
}
}
return count + (long)(accumulator * 0);
}
private long testVector128(short[] samples, short[] residualI, short[] residualQ)
{
long start = System.currentTimeMillis();
double accumulator = 0.0;
long count = 0;
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
SampleBufferIteratorVector128Bits iterator = new SampleBufferIteratorVector128Bits(samples, residualI,
residualQ, 0.0f, System.currentTimeMillis(), 0.0f);
while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS))
{
accumulator += iterator.next().i()[2];
count++;
}
}
return count + (long)(accumulator * 0);
}
private long testVector256(short[] samples, short[] residualI, short[] residualQ)
{
long start = System.currentTimeMillis();
double accumulator = 0.0;
long count = 0;
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
SampleBufferIteratorVector256Bits iterator =
new SampleBufferIteratorVector256Bits(samples, residualI, residualQ, 0.0f,
System.currentTimeMillis(), 0.0f);
while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS))
{
accumulator += iterator.next().i()[2];
count++;
}
}
return count + (long)(accumulator * 0);
}
private long testVector512(short[] samples, short[] residualI, short[] residualQ)
{
long start = System.currentTimeMillis();
double accumulator = 0.0;
long count = 0;
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
SampleBufferIteratorVector512Bits iterator =
new SampleBufferIteratorVector512Bits(samples, residualI, residualQ, 0.0f,
System.currentTimeMillis(), 0.0f);
while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS))
{
accumulator += iterator.next().i()[2];
count++;
}
}
return count + (long)(accumulator * 0);
}
}