diff --git a/build.gradle b/build.gradle
index 3a88718d..836a43c9 100644
--- a/build.gradle
+++ b/build.gradle
@@ -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
diff --git a/settings.gradle b/settings.gradle
index f4985b54..0cc4eac9 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -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'
diff --git a/src/main/java/io/github/dsheirer/buffer/sample/ISampleConverter.java b/src/main/java/io/github/dsheirer/buffer/sample/ISampleConverter.java
deleted file mode 100644
index 906f9144..00000000
--- a/src/main/java/io/github/dsheirer/buffer/sample/ISampleConverter.java
+++ /dev/null
@@ -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
- * ****************************************************************************
- */
-
-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();
-}
diff --git a/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIterator.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIterator.java
deleted file mode 100644
index 9567aeab..00000000
--- a/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIterator.java
+++ /dev/null
@@ -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
- * ****************************************************************************
- */
-
-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 either ComplexSamples or InterleavedComplexSamples
- */
-public abstract class SampleBufferIterator implements Iterator
-{
- 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;
- }
-}
diff --git a/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorScalar.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorScalar.java
deleted file mode 100644
index 383af553..00000000
--- a/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorScalar.java
+++ /dev/null
@@ -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
- * ****************************************************************************
- */
-
-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
-{
- /**
- * 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);
- }
-}
diff --git a/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector128Bits.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector128Bits.java
deleted file mode 100644
index 29a8a4b2..00000000
--- a/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector128Bits.java
+++ /dev/null
@@ -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
- * ****************************************************************************
- */
-
-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
-{
- private static final VectorSpecies 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);
- }
-}
diff --git a/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector256Bits.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector256Bits.java
deleted file mode 100644
index 9280c4cc..00000000
--- a/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector256Bits.java
+++ /dev/null
@@ -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
- * ****************************************************************************
- */
-
-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
-{
- private static final VectorSpecies 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);
- }
-}
diff --git a/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector512Bits.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector512Bits.java
deleted file mode 100644
index ee7f844f..00000000
--- a/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector512Bits.java
+++ /dev/null
@@ -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
- * ****************************************************************************
- */
-
-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
-{
- private static final VectorSpecies 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);
- }
-}
diff --git a/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector64Bits.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector64Bits.java
deleted file mode 100644
index dbaacfae..00000000
--- a/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector64Bits.java
+++ /dev/null
@@ -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
- * ****************************************************************************
- */
-
-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
-{
- private static final VectorSpecies 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);
- }
-}
diff --git a/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorScalar.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorScalar.java
deleted file mode 100644
index 0540d626..00000000
--- a/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorScalar.java
+++ /dev/null
@@ -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
- * ****************************************************************************
- */
-
-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
-{
- /**
- * 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);
- }
-}
diff --git a/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector128Bits.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector128Bits.java
deleted file mode 100644
index 205656e2..00000000
--- a/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector128Bits.java
+++ /dev/null
@@ -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
- * ****************************************************************************
- */
-
-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
-{
- private static final VectorSpecies 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);
- }
-}
diff --git a/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector256Bits.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector256Bits.java
deleted file mode 100644
index 3951472a..00000000
--- a/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector256Bits.java
+++ /dev/null
@@ -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
- * ****************************************************************************
- */
-
-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
-{
- private static final VectorSpecies 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);
- }
-}
diff --git a/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector512Bits.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector512Bits.java
deleted file mode 100644
index cf6df573..00000000
--- a/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector512Bits.java
+++ /dev/null
@@ -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
- * ****************************************************************************
- */
-
-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
-{
- private static final VectorSpecies 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);
- }
-}
diff --git a/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector64Bits.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector64Bits.java
deleted file mode 100644
index 8859565a..00000000
--- a/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector64Bits.java
+++ /dev/null
@@ -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
- * ****************************************************************************
- */
-
-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
-{
- private static final VectorSpecies 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);
- }
-}
diff --git a/src/main/java/io/github/dsheirer/buffer/sample/SampleNativeBuffer.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleNativeBuffer.java
deleted file mode 100644
index 9554b2a9..00000000
--- a/src/main/java/io/github/dsheirer/buffer/sample/SampleNativeBuffer.java
+++ /dev/null
@@ -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
- * ****************************************************************************
- */
-
-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 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 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());
- };
- }
-}
\ No newline at end of file
diff --git a/src/main/java/io/github/dsheirer/buffer/sample/SampleNativeBufferFactory.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleNativeBufferFactory.java
deleted file mode 100644
index 0efea14c..00000000
--- a/src/main/java/io/github/dsheirer/buffer/sample/SampleNativeBufferFactory.java
+++ /dev/null
@@ -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
- * ****************************************************************************
- */
-
-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)];
- }
- }
-}
diff --git a/src/main/java/io/github/dsheirer/buffer/sample/ScalarPackedSampleConverter.java b/src/main/java/io/github/dsheirer/buffer/sample/ScalarPackedSampleConverter.java
deleted file mode 100644
index 7bcd3a7c..00000000
--- a/src/main/java/io/github/dsheirer/buffer/sample/ScalarPackedSampleConverter.java
+++ /dev/null
@@ -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
- * ****************************************************************************
- */
-
-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");
- }
-}
diff --git a/src/main/java/io/github/dsheirer/buffer/sample/ScalarUnpackedSampleConverter.java b/src/main/java/io/github/dsheirer/buffer/sample/ScalarUnpackedSampleConverter.java
deleted file mode 100644
index 50ad184f..00000000
--- a/src/main/java/io/github/dsheirer/buffer/sample/ScalarUnpackedSampleConverter.java
+++ /dev/null
@@ -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
- * ****************************************************************************
- */
-
-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();
- }
-}
diff --git a/src/main/java/io/github/dsheirer/buffer/sample/VectorUnpackedSampleConverter.java b/src/main/java/io/github/dsheirer/buffer/sample/VectorUnpackedSampleConverter.java
deleted file mode 100644
index 9c018207..00000000
--- a/src/main/java/io/github/dsheirer/buffer/sample/VectorUnpackedSampleConverter.java
+++ /dev/null
@@ -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
- * ****************************************************************************
- */
-
-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 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();
- }
-}
diff --git a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyDeviceInformation.java b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyDeviceInformation.java
index 2b6b2be3..8bb05538 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyDeviceInformation.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyDeviceInformation.java
@@ -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
+ * ****************************************************************************
+ */
+
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();
}
}
diff --git a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTuner.java b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTuner.java
index 17014fac..7c9a5248 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTuner.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTuner.java
@@ -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
diff --git a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerController.java
index b9abf805..b43edfe3 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerController.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerController.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2025 Dennis Sheirer
+ * 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
@@ -19,31 +19,40 @@
package io.github.dsheirer.source.tuner.airspy;
-import io.github.dsheirer.buffer.INativeBufferFactory;
-import io.github.dsheirer.buffer.sample.SampleNativeBufferFactory;
+import io.github.dsheirer.airspy.Airspy;
+import io.github.dsheirer.airspy.AirspyDevice;
+import io.github.dsheirer.airspy.AirspyException;
+import io.github.dsheirer.airspy.SampleBuffer;
+import io.github.dsheirer.airspy.SampleType;
+import io.github.dsheirer.buffer.FloatNativeBuffer;
+import io.github.dsheirer.buffer.INativeBuffer;
+import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.ITunerErrorListener;
+import io.github.dsheirer.source.tuner.TunerController;
import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
-import io.github.dsheirer.source.tuner.usb.USBTunerController;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
+import io.github.dsheirer.source.tuner.manager.TunerManager;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
-import org.apache.commons.io.EndianUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.usb4java.Context;
+import org.usb4java.Device;
+import org.usb4java.DeviceDescriptor;
+import org.usb4java.DeviceHandle;
+import org.usb4java.DeviceList;
import org.usb4java.LibUsb;
-import org.usb4java.LibUsbException;
-
-import javax.usb.UsbException;
/**
- * Airspy tuner controller
+ * Airspy tuner controller, implemented with the jAirspy library (libairspy).
+ *
+ * The tuner is discovered with LibUsb at a USB bus and port, like all other USB tuners, however libairspy owns the
+ * USB device, its transfers, and the conversion of real samples to complex samples.
*/
-public class AirspyTunerController extends USBTunerController
+public class AirspyTunerController extends TunerController
{
private final static Logger mLog = LoggerFactory.getLogger(AirspyTunerController.class);
public static final Gain LINEARITY_GAIN_DEFAULT = Gain.LINEARITY_14;
@@ -63,16 +72,21 @@ public class AirspyTunerController extends USBTunerController
public static final long MAXIMUM_TUNABLE_FREQUENCY_HZ = 1800000000l;
public static final long FREQUENCY_DEFAULT = 101100000;
public static final double USABLE_BANDWIDTH_PERCENT = 0.90;
- private static final long USB_TIMEOUT_MS = 2000l; //milliseconds
- private static final int USB_TRANSFER_BUFFER_SIZE = 262144;
- private static final byte USB_REQUEST_IN = (byte) (LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE);
- private static final byte USB_REQUEST_OUT = (byte) (LibUsb.ENDPOINT_OUT | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE);
public static final DecimalFormat MHZ_FORMATTER = new DecimalFormat("#.00 MHz");
public static final AirspySampleRate DEFAULT_SAMPLE_RATE = new AirspySampleRate(0, 10000000, "10.00 MHz");
- private SampleNativeBufferFactory mNativeBufferFactory = new SampleNativeBufferFactory();
+ //libairspy uses 262,144 byte (unpacked) transfers of 16-bit real samples, which it converts to 65,536 complex samples
+ private static final int BUFFER_SAMPLE_COUNT = 65536;
+ //Complex samples from libairspy are half the amplitude (-6 dB) of the analytic signal for a real input. Restoring
+ //this keeps spectrum levels and squelch thresholds consistent with the previous Java sample converter.
+ private static final float SAMPLE_SCALE = 2.0f;
+ private final int mBus;
+ private final String mPortAddress;
+ private AirspyDevice mDevice;
private AirspyDeviceInformation mDeviceInfo;
private List mSampleRates = new ArrayList<>();
private int mSampleRate = 0;
+ private volatile float mSamplesPerMillisecond = 0.0f;
+ private boolean mStreaming = false;
/**
* Constructs an instance
@@ -82,7 +96,13 @@ public class AirspyTunerController extends USBTunerController
*/
public AirspyTunerController(int bus, String portAddress, ITunerErrorListener tunerErrorListener)
{
- super(bus, portAddress, MINIMUM_TUNABLE_FREQUENCY_HZ, MAXIMUM_TUNABLE_FREQUENCY_HZ, 0, USABLE_BANDWIDTH_PERCENT, tunerErrorListener);
+ super(tunerErrorListener);
+ mBus = bus;
+ mPortAddress = portAddress;
+ setMinimumFrequency(MINIMUM_TUNABLE_FREQUENCY_HZ);
+ setMaximumFrequency(MAXIMUM_TUNABLE_FREQUENCY_HZ);
+ setMiddleUnusableHalfBandwidth(0);
+ setUsableBandwidthPercentage(USABLE_BANDWIDTH_PERCENT);
}
@Override
@@ -91,77 +111,333 @@ public class AirspyTunerController extends USBTunerController
return TunerType.AIRSPY_R820T;
}
- @Override
- protected INativeBufferFactory getNativeBufferFactory()
- {
- return mNativeBufferFactory;
- }
-
- @Override
- protected int getTransferBufferSize()
- {
- return USB_TRANSFER_BUFFER_SIZE;
- }
-
@Override
public int getBufferSampleCount()
{
- //Each airspy complex sample is 4 bytes, so sample count is buffer size / 4
- return USB_TRANSFER_BUFFER_SIZE / 4;
+ return BUFFER_SAMPLE_COUNT;
}
@Override
- protected void deviceStart() throws SourceException
+ public void start() throws SourceException
{
- try
- {
- setSamplePacking(false);
- }
- catch(LibUsbException | UsbException | UnsupportedOperationException e)
- {
- mLog.info("Sample packing is not supported by airspy firmware");
- }
+ long serialNumber = readSerialNumber();
+
+ getLock().lock();
try
{
- setReceiverMode(true);
- }
- catch(Exception e)
- {
- mLog.error("Couldn't enable airspy receiver mode", e);
- }
+ mDevice = Airspy.open(serialNumber);
- setFrequency(FREQUENCY_DEFAULT);
+ //Sample packing is off by default in libairspy and the sample type must be set before reading sample
+ //rates, since libairspy reports real sample rates as twice the complex sample rates.
+ mDevice.setSampleType(SampleType.FLOAT32_IQ);
+ mDeviceInfo = new AirspyDeviceInformation(mDevice.boardName(), mDevice.firmwareVersion(),
+ mDevice.partIdSerialNumber());
+ mSampleRates.clear();
- try
- {
- determineAvailableSampleRates();
- }
- catch(LibUsbException | UsbException e)
- {
- mLog.error("Error identifying available samples rates", e);
- }
+ for(long rate : mDevice.sampleRates())
+ {
+ mSampleRates.add(new AirspySampleRate(mSampleRates.size(), (int)rate, formatSampleRate((int)rate)));
+ }
- try
- {
+ if(mSampleRates.isEmpty())
+ {
+ mSampleRates.add(DEFAULT_SAMPLE_RATE);
+ }
+
+ setFrequency(FREQUENCY_DEFAULT);
setSampleRate(mSampleRates.get(0));
}
- catch(IllegalArgumentException | LibUsbException | UsbException e)
+ catch(AirspyException | SourceException | LinkageError e)
{
- mLog.error("Setting sample rate is not supported by firmware", e);
+ closeDevice(mDevice);
+ mDevice = null;
+ throw new SourceException("Can't start Airspy tuner - " + e.getMessage(), e);
+ }
+ finally
+ {
+ getLock().unlock();
}
}
@Override
- protected void deviceStop()
+ public void stop()
+ {
+ AirspyDevice device;
+
+ getLock().lock();
+
+ try
+ {
+ device = mDevice;
+ mDevice = null;
+ mStreaming = false;
+ }
+ finally
+ {
+ getLock().unlock();
+ }
+
+ mNativeBufferBroadcaster.clear();
+ closeDevice(device);
+ }
+
+ /**
+ * Closes the device, which also stops streaming.
+ * @param device to close, or null
+ */
+ private static void closeDevice(AirspyDevice device)
+ {
+ if(device != null)
+ {
+ try
+ {
+ device.close();
+ }
+ catch(AirspyException | RuntimeException e)
+ {
+ mLog.error("Error closing Airspy device", e);
+ }
+ }
+ }
+
+ /**
+ * Reads the serial number of the device at this controller's USB bus and port, since libairspy opens a specific
+ * device by serial number. The device is closed again before returning so that libairspy can open it.
+ * @return serial number
+ * @throws SourceException if the device isn't found or can't be accessed.
+ */
+ private long readSerialNumber() throws SourceException
+ {
+ Context context = new Context();
+ int status = LibUsb.init(context);
+
+ if(status != LibUsb.SUCCESS)
+ {
+ throw new SourceException("Can't initialize libusb library - " + LibUsb.errorName(status));
+ }
+
+ try
+ {
+ DeviceList deviceList = new DeviceList();
+ int count = LibUsb.getDeviceList(context, deviceList);
+
+ if(count < 0)
+ {
+ throw new SourceException("Can't get the USB device list - " + LibUsb.errorName(count));
+ }
+
+ try
+ {
+ for(Device device : deviceList)
+ {
+ if(LibUsb.getBusNumber(device) == mBus && LibUsb.getPortNumber(device) > 0 &&
+ mPortAddress != null && mPortAddress.equals(TunerManager.getPortAddress(device)))
+ {
+ return readSerialNumber(device);
+ }
+ }
+ }
+ finally
+ {
+ LibUsb.freeDeviceList(deviceList, true);
+ }
+ }
+ finally
+ {
+ LibUsb.exit(context);
+ }
+
+ throw new SourceException("Couldn't find USB device at bus [" + mBus + "] port [" + mPortAddress + "]");
+ }
+
+ /**
+ * Reads the serial number from the device's USB serial string, formatted as: AIRSPY SN:0123456789ABCDEF
+ * @param device to read
+ * @return serial number
+ * @throws SourceException if the device can't be opened or the serial string isn't valid.
+ */
+ private static long readSerialNumber(Device device) throws SourceException
+ {
+ DeviceDescriptor descriptor = new DeviceDescriptor();
+ int status = LibUsb.getDeviceDescriptor(device, descriptor);
+
+ if(status != LibUsb.SUCCESS)
+ {
+ throw new SourceException("Can't obtain tuner's device descriptor - " + LibUsb.errorName(status));
+ }
+
+ DeviceHandle handle = new DeviceHandle();
+ status = LibUsb.open(device, handle);
+
+ if(status == LibUsb.ERROR_ACCESS)
+ {
+ mLog.error("Access to USB tuner denied - (windows) reinstall zadig driver or (linux) blacklist driver and/or check udev rules");
+ throw new SourceException("access denied - if using linux, blacklist the default driver and/or install udev rules");
+ }
+ else if(status != LibUsb.SUCCESS)
+ {
+ mLog.error("Can't open USB tuner - check driver or Linux udev rules");
+ throw new SourceException("Can't open USB tuner - reinstall driver? - " + LibUsb.errorName(status));
+ }
+
+ String serial;
+
+ try
+ {
+ serial = LibUsb.getStringDescriptor(handle, descriptor.iSerialNumber());
+ }
+ finally
+ {
+ LibUsb.close(handle);
+ }
+
+ try
+ {
+ return Long.parseUnsignedLong(serial.substring(serial.lastIndexOf(':') + 1), 16);
+ }
+ catch(NullPointerException | NumberFormatException e)
+ {
+ throw new SourceException("Can't read Airspy serial number from USB serial string [" + serial + "]", e);
+ }
+ }
+
+ /**
+ * Operation that requires the device.
+ */
+ private interface DeviceOperation
+ {
+ void execute(AirspyDevice device) throws AirspyException;
+ }
+
+ /**
+ * Executes the operation under the controller lock so that the device can't be closed while it is in use. The
+ * operation is ignored when the device isn't open, which allows for calls that arrive during tuner shutdown.
+ * @param operation to execute
+ * @throws SourceException if the operation fails
+ */
+ private void execute(DeviceOperation operation) throws SourceException
+ {
+ getLock().lock();
+
+ try
+ {
+ if(mDevice != null)
+ {
+ operation.execute(mDevice);
+ }
+ }
+ catch(AirspyException e)
+ {
+ throw new SourceException(e.getMessage(), e);
+ }
+ finally
+ {
+ getLock().unlock();
+ }
+ }
+
+ /**
+ * Receives converted samples on the libairspy thread and dispatches them to the buffer listeners. Exceptions
+ * are contained here since an exception reaching the library would stop the sample stream.
+ * @param buffer from the library, which is only valid until this method returns.
+ * @return true to continue streaming
+ */
+ private boolean receive(SampleBuffer buffer)
{
try
{
- setReceiverMode(false);
+ float[] samples = buffer.copyFloats();
+
+ for(int x = 0; x < samples.length; x++)
+ {
+ samples[x] *= SAMPLE_SCALE;
+ }
+
+ broadcast(new FloatNativeBuffer(samples, System.currentTimeMillis(), mSamplesPerMillisecond));
}
- catch(Exception e)
+ catch(RuntimeException e)
{
- mLog.error("Couldn't enable airspy receiver mode", e);
+ mLog.error("Error dispatching Airspy sample buffer", e);
+ }
+
+ return true;
+ }
+
+ /**
+ * Starts the sample stream if it's not already started.
+ */
+ private void startStreaming() throws SourceException
+ {
+ if(!mStreaming)
+ {
+ execute(device -> device.startRx(this::receive));
+ mStreaming = mDevice != null;
+ }
+ }
+
+ /**
+ * Stops the sample stream if it's started.
+ */
+ private void stopStreaming() throws SourceException
+ {
+ if(mStreaming)
+ {
+ mStreaming = false;
+ execute(AirspyDevice::stopRx);
+ }
+ }
+
+ /**
+ * Adds the IQ buffer listener and automatically starts streaming, if not already started.
+ */
+ @Override
+ public void addBufferListener(Listener listener)
+ {
+ getLock().lock();
+
+ try
+ {
+ if(mDevice != null)
+ {
+ super.addBufferListener(listener);
+ startStreaming();
+ }
+ }
+ catch(SourceException se)
+ {
+ mLog.error("Error starting streaming on Airspy tuner", se);
+ }
+ finally
+ {
+ getLock().unlock();
+ }
+ }
+
+ /**
+ * Removes the IQ buffer listener and stops streaming if there are no more listeners.
+ */
+ @Override
+ public void removeBufferListener(Listener listener)
+ {
+ getLock().lock();
+
+ try
+ {
+ super.removeBufferListener(listener);
+
+ if(!hasBufferListeners())
+ {
+ stopStreaming();
+ }
+ }
+ catch(SourceException se)
+ {
+ mLog.error("Error stopping streaming on Airspy tuner", se);
+ }
+ finally
+ {
+ getLock().unlock();
}
}
@@ -173,29 +449,8 @@ public class AirspyTunerController extends USBTunerController
if(tunerConfiguration instanceof AirspyTunerConfiguration config)
{
- int sampleRate = config.getSampleRate();
- AirspySampleRate rate = getSampleRate(sampleRate);
-
- if(rate == null)
- {
- if(!mSampleRates.isEmpty())
- {
- rate = mSampleRates.get(0);
- }
- else
- {
- rate = DEFAULT_SAMPLE_RATE;
- }
- }
-
- try
- {
- setSampleRate(rate);
- }
- catch(UsbException e)
- {
- throw new SourceException("Couldn't set sample rate [" + rate.toString() + "]", e);
- }
+ AirspySampleRate rate = getSampleRate(config.getSampleRate());
+ setSampleRate(rate != null ? rate : mSampleRates.isEmpty() ? DEFAULT_SAMPLE_RATE : mSampleRates.get(0));
try
{
@@ -230,24 +485,11 @@ public class AirspyTunerController extends USBTunerController
}
@Override
- public synchronized void setTunedFrequency(long frequency) throws SourceException
+ public void setTunedFrequency(long frequency) throws SourceException
{
if(MINIMUM_TUNABLE_FREQUENCY_HZ <= frequency && frequency <= MAXIMUM_TUNABLE_FREQUENCY_HZ)
{
- ByteBuffer buffer = ByteBuffer.allocateDirect(4);
- buffer.order(ByteOrder.LITTLE_ENDIAN);
- buffer.putInt((int) frequency);
- buffer.rewind();
-
- try
- {
- write(Command.SET_FREQUENCY, 0, 0, buffer);
- }
- catch(UsbException e)
- {
- mLog.error("error setting frequency [" + frequency + "]", e);
- throw new SourceException("error setting frequency [" + frequency + "]", e);
- }
+ execute(device -> device.setFrequency(frequency));
}
else
{
@@ -262,49 +504,63 @@ public class AirspyTunerController extends USBTunerController
}
/**
- * Sets the sample rate to the rate specified by the index value in the available sample rates map
+ * Sets the sample rate. The sample stream is stopped for the change, because libairspy clears the halt on the
+ * streaming USB endpoint when it changes the sample rate.
*
- * @param rate to a sample rate in the available samples rates map.
- * @throws IllegalArgumentException if index is not a valid rate index
- * @throws LibUsbException if there was a read error or if this operation is not supported by the current firmware
- * @throws UsbException if there was a USB error
+ * @param rate to set
+ * @throws SourceException if the rate can't be set
*/
- public void setSampleRate(AirspySampleRate rate) throws LibUsbException, UsbException, SourceException
+ public void setSampleRate(AirspySampleRate rate) throws SourceException
{
- getNativeBufferFactory().setSamplesPerMillisecond((float)rate.getRate() / 1000.0f);
- if(rate.getRate() != mSampleRate)
+ getLock().lock();
+
+ try
{
- int result = readByte(Command.SET_SAMPLE_RATE, 0, rate.getIndex(), true);
+ mSamplesPerMillisecond = (float)rate.getRate() / 1000.0f;
- if(result != 1)
+ if(rate.getRate() != mSampleRate)
{
- throw new UsbException("Error setting sample rate [" + rate + "] rate - return value [" + result + "]");
- }
- else
- {
- mSampleRate = rate.getRate();
- mFrequencyController.setSampleRate(mSampleRate);
+ boolean streaming = mStreaming;
+ stopStreaming();
- // Update the Usable Bandwidth Percentage, as the usable range varies with the sample rate.
- // Values below are from https://github.com/DSheirer/sdrtrunk/issues/815#issuecomment-699859590
- switch(mSampleRate)
+ try
{
- default:
- case 10000000:
- super.setUsableBandwidthPercentage(0.90);
- break;
- case 6000000:
- super.setUsableBandwidthPercentage(0.83);
- break;
- case 3000000:
- super.setUsableBandwidthPercentage(0.66);
- break;
- case 2500000:
- super.setUsableBandwidthPercentage(0.60);
- break;
+ execute(device -> device.setSampleRate(rate.getRate()));
+ mSampleRate = rate.getRate();
+ mFrequencyController.setSampleRate(mSampleRate);
+
+ // Update the Usable Bandwidth Percentage, as the usable range varies with the sample rate.
+ // Values below are from https://github.com/DSheirer/sdrtrunk/issues/815#issuecomment-699859590
+ switch(mSampleRate)
+ {
+ default:
+ case 10000000:
+ super.setUsableBandwidthPercentage(0.90);
+ break;
+ case 6000000:
+ super.setUsableBandwidthPercentage(0.83);
+ break;
+ case 3000000:
+ super.setUsableBandwidthPercentage(0.66);
+ break;
+ case 2500000:
+ super.setUsableBandwidthPercentage(0.60);
+ break;
+ }
+ }
+ finally
+ {
+ if(streaming)
+ {
+ startStreaming();
+ }
}
}
}
+ finally
+ {
+ getLock().unlock();
+ }
}
/**
@@ -342,62 +598,29 @@ public class AirspyTunerController extends USBTunerController
return null;
}
- /**
- * Enables/Disables sample packing to allow two 12-bit samples to be packed into 3 bytes (enabled) or 4 bytes (disabled).
- *
- * @param enabled true or false
- * @throws UsbException if sample packing is not supported by the current device firmware or if there were usb
- * communication issues
- */
- public void setSamplePacking(boolean enabled) throws LibUsbException, UsbException
- {
- int result = readByte(Command.SET_PACKING, 0, (enabled ? 1 : 0), true);
-
- if(result != 1)
- {
- throw new UsbException("Couldnt set sample packing enabled: " + enabled);
- }
-
- /* If we didn't throw an exception above, then update the sample adapter
- * to process samples accordingly */
- mNativeBufferFactory.setSamplePacking(enabled);
- }
-
/**
* Enables/disables the mixer automatic gain setting
*
- * @param enabled
- * @throws LibUsbException on unsuccessful read operation
- * @throws UsbException on USB error
+ * @param enabled true to enable
+ * @throws SourceException on error
*/
- public synchronized void setMixerAGC(boolean enabled) throws LibUsbException, UsbException
+ public void setMixerAGC(boolean enabled) throws SourceException
{
- int result = readByte(Command.SET_MIXER_AGC, 0, (enabled ? 1 : 0), true);
-
- if(result != LibUsb.SUCCESS)
- {
- throw new UsbException("Couldnt set mixer AGC enabled: " + enabled);
- }
+ execute(device -> device.setMixerAgc(enabled));
}
/**
* Enables/disables the low noise amplifier automatic gain setting
*
- * @param enabled
- * @throws LibUsbException on unsuccessful read operation
- * @throws UsbException on USB error
+ * @param enabled true to enable
+ * @throws SourceException on error
*/
- public synchronized void setLNAAGC(boolean enabled) throws LibUsbException, UsbException
+ public void setLNAAGC(boolean enabled) throws SourceException
{
- int result = readByte(Command.SET_LNA_AGC, 0, (enabled ? 1 : 0), true);
-
- if(result != LibUsb.SUCCESS)
- {
- throw new UsbException("Couldn't set LNA AGC enabled: " + enabled);
- }
+ execute(device -> device.setLnaAgc(enabled));
}
- public void setGain(Gain gain) throws UsbException
+ public void setGain(Gain gain) throws SourceException
{
if(gain != Gain.CUSTOM)
{
@@ -413,20 +636,14 @@ public class AirspyTunerController extends USBTunerController
* Sets LNA gain
*
* @param gain - value within range of LNA_GAIN_MIN to LNA_GAIN_MAX
- * @throws LibUsbException on error in java USB wrapper
- * @throws UsbException on error in USB transfer
+ * @throws SourceException on error
* @throws IllegalArgumentException if gain value is invalid
*/
- public synchronized void setLNAGain(int gain) throws LibUsbException, UsbException, IllegalArgumentException
+ public void setLNAGain(int gain) throws SourceException, IllegalArgumentException
{
if(LNA_GAIN_MIN <= gain && gain <= LNA_GAIN_MAX)
{
- int result = readByte(Command.SET_LNA_GAIN, 0, gain, true);
-
- if(result != LibUsb.SUCCESS)
- {
- throw new UsbException("Couldnt set LNA gain to: " + gain);
- }
+ execute(device -> device.setLnaGain(gain));
}
else
{
@@ -439,20 +656,14 @@ public class AirspyTunerController extends USBTunerController
* Sets Mixer gain
*
* @param gain - value within range of MIXER_GAIN_MIN to MIXER_GAIN_MAX
- * @throws LibUsbException on error in java USB wrapper
- * @throws UsbException on error in USB transfer
+ * @throws SourceException on error
* @throws IllegalArgumentException if gain value is invalid
*/
- public void setMixerGain(int gain) throws LibUsbException, UsbException, IllegalArgumentException
+ public void setMixerGain(int gain) throws SourceException, IllegalArgumentException
{
if(MIXER_GAIN_MIN <= gain && gain <= MIXER_GAIN_MAX)
{
- int result = readByte(Command.SET_MIXER_GAIN, 0, gain, true);
-
- if(result != LibUsb.SUCCESS)
- {
- throw new UsbException("Couldnt set mixer gain to: " + gain);
- }
+ execute(device -> device.setMixerGain(gain));
}
else
{
@@ -464,21 +675,15 @@ public class AirspyTunerController extends USBTunerController
/**
* Sets IF (VGA) gain
*
- * @param gain - value within range of VGA_GAIN_MIN to VGA_GAIN_MAX
- * @throws LibUsbException on error in java USB wrapper
- * @throws UsbException on error in USB transfer
+ * @param gain - value within range of IF_GAIN_MIN to IF_GAIN_MAX
+ * @throws SourceException on error
* @throws IllegalArgumentException if gain value is invalid
*/
- public void setIFGain(int gain) throws LibUsbException, UsbException, IllegalArgumentException
+ public void setIFGain(int gain) throws SourceException, IllegalArgumentException
{
if(IF_GAIN_MIN <= gain && gain <= IF_GAIN_MAX)
{
- int result = readByte(Command.SET_VGA_GAIN, 0, gain, true);
-
- if(result != LibUsb.SUCCESS)
- {
- throw new UsbException("Couldnt set VGA gain to: " + gain);
- }
+ execute(device -> device.setVgaGain(gain));
}
else
{
@@ -487,53 +692,6 @@ public class AirspyTunerController extends USBTunerController
}
}
- public void setReceiverMode(boolean enabled) throws LibUsbException, UsbException
- {
- //Empty buffer to throw away
- ByteBuffer buffer = ByteBuffer.allocateDirect(0);
- write(Command.RECEIVER_MODE, (enabled ? 1 : 0), 0, buffer);
- }
-
- /**
- * Queries the device for available sample rates. Will always provide at
- * least the default 10 MHz sample rate.
- */
- private void determineAvailableSampleRates() throws LibUsbException, UsbException
- {
- mSampleRates.clear();
-
- //Get a count of available sample rates. If we get an exception, then
- //we're using an older firmware revision and only the default 10 MHz
- //rate is supported
- try
- {
- byte[] rawCount = readArray(Command.GET_SAMPLE_RATES, 0, 0, 4);
-
- if(rawCount != null)
- {
- int count = EndianUtils.readSwappedInteger(rawCount, 0);
-
- byte[] rawRates = readArray(Command.GET_SAMPLE_RATES, 0,
- count, (count * 4));
-
- for(int x = 0; x < count; x++)
- {
- int rate = EndianUtils.readSwappedInteger(rawRates, (x * 4));
- mSampleRates.add(new AirspySampleRate(x, rate, formatSampleRate(rate)));
- }
- }
- }
- catch(LibUsbException e)
- {
- //Press on, nothing else to do here ..
- }
-
- if(mSampleRates.isEmpty())
- {
- mSampleRates.add(DEFAULT_SAMPLE_RATE);
- }
- }
-
/**
* Formats the rate in hertz for display as megahertz
*/
@@ -543,166 +701,13 @@ public class AirspyTunerController extends USBTunerController
}
/**
- * Device information
+ * Device information, available once the tuner has been started.
*/
public AirspyDeviceInformation getDeviceInfo()
{
- //Lazy initialization
- if(mDeviceInfo == null)
- {
- readDeviceInfo();
- }
-
return mDeviceInfo;
}
- /**
- * Reads version information from the device and populates the info object
- */
- private void readDeviceInfo()
- {
- if(mDeviceInfo == null)
- {
- mDeviceInfo = new AirspyDeviceInformation();
- }
-
- /* Board ID */
- try
- {
- int boardID = readByte(Command.BOARD_ID_READ, 0, 0, true);
- mDeviceInfo.setBoardID(boardID);
- }
- catch(LibUsbException | UsbException e)
- {
- mLog.error("Error reading airspy board ID", e);
- }
-
- /* Version String */
- try
- {
- //NOTE: libairspy is internally reading 127 bytes, however airspy_info script is telling it to read
- //255 bytes ... things that make you go hmmmm
- byte[] version = readArray(Command.VERSION_STRING_READ, 0, 0, 127);
- mDeviceInfo.setVersion(version);
- }
- catch(LibUsbException | UsbException e)
- {
- mLog.error("Error reading airspy version string", e);
- }
-
- /* Part ID and Serial Number */
- try
- {
- //Read 6 x 32-bit integers = 24 bytes
- byte[] serial = readArray(Command.BOARD_PART_ID_SERIAL_NUMBER_READ, 0, 0, 24);
- mDeviceInfo.setPartAndSerialNumber(serial);
- }
- catch(LibUsbException | UsbException e)
- {
- mLog.error("Error reading airspy version string", e);
- }
- }
-
- /**
- * Reads a single byte value from the device.
- *
- * @param command - airspy command
- * @param value - value field for usb setup packet
- * @param index - index field for usb setup packet
- * @return - byte value as an integer
- * @throws LibUsbException if the operation is unsuccesful
- * @throws UsbException on any usb errors
- */
- private int readByte(Command command, int value, int index, boolean signed) throws LibUsbException, UsbException
- {
- if(isRunning())
- {
- ByteBuffer buffer = ByteBuffer.allocateDirect(1);
-
- int transferred = LibUsb.controlTransfer(getDeviceHandle(), USB_REQUEST_IN, command.getValue(),
- (short) value, (short) index, buffer, USB_TIMEOUT_MS);
-
- if(transferred < 0)
- {
- throw new LibUsbException("read error", transferred);
- }
-
- byte result = buffer.get(0);
-
- if(signed)
- {
- return (result & 0xFF);
- }
- else
- {
- return result;
- }
- }
-
- return 0;
- }
-
- /**
- * Reads a multi-byte value from the device
- *
- * @param command - airspy command
- * @param value - usb packet value
- * @param index - usb packet index
- * @param length - number of bytes to read
- * @return - bytes read from the device
- * @throws LibUsbException if quantity of bytes read doesn't equal the requested number of bytes
- * @throws UsbException on error communicating with the device
- */
- private byte[] readArray(Command command, int value, int index, int length) throws LibUsbException, UsbException
- {
- if(isRunning())
- {
- ByteBuffer buffer = ByteBuffer.allocateDirect(length);
-
- int transferred = LibUsb.controlTransfer(getDeviceHandle(), USB_REQUEST_IN, command.getValue(), (short) value,
- (short) index, buffer, USB_TIMEOUT_MS);
-
- if(transferred < 0)
- {
- throw new LibUsbException("read error", transferred);
- }
-
- byte[] results = new byte[transferred];
- buffer.get(results);
- return results;
- }
-
- return new byte[0];
- }
-
- /**
- * Writes the buffer contents to the device
- *
- * @param command - airspy command
- * @param value - usb packet value
- * @param index - usb packet index
- * @param buffer - data to write to the device
- * @throws UsbException on error
- */
- public void write(Command command, int value, int index, ByteBuffer buffer) throws UsbException
- {
- if(isRunning())
- {
- int transferred = LibUsb.controlTransfer(getDeviceHandle(), USB_REQUEST_OUT, command.getValue(),
- (short) value, (short) index, buffer, USB_TIMEOUT_MS);
-
- if(transferred < 0)
- {
- throw new LibUsbException("error writing byte buffer", transferred);
- }
- else if(transferred != buffer.capacity())
- {
- throw new LibUsbException("transferred bytes [" + transferred + "] is not what was expected [" +
- buffer.capacity() + "]", transferred);
- }
- }
- }
-
public enum GainMode
{
LINEARITY,
@@ -845,194 +850,4 @@ public class AirspyTunerController extends USBTunerController
return EnumSet.range(SENSITIVITY_1, SENSITIVITY_22);
}
}
-
- /**
- * Airspy Board Identifier
- */
- public enum BoardID
- {
- AIRSPY(0, "Airspy"),
- UNKNOWN(-1, "Unknown");
-
- private int mValue;
- private String mLabel;
-
- private BoardID(int value, String label)
- {
- mValue = value;
- mLabel = label;
- }
-
- public int getValue()
- {
- return mValue;
- }
-
- public String getLabel()
- {
- return mLabel;
- }
-
- public static BoardID fromValue(int value)
- {
- if(value == 0)
- {
- return AIRSPY;
- }
-
- return UNKNOWN;
- }
- }
-
- /**
- * Airspy Commands
- */
- public enum Command
- {
- INVALID(0),
- RECEIVER_MODE(1),
- SI5351C_WRITE(2),
- SI5351C_READ(3),
- R820T_WRITE(4),
- R820T_READ(5),
- SPIFLASH_ERASE(6),
- SPIFLASH_WRITE(7),
- SPIFLASH_READ(8),
- BOARD_ID_READ(9),
- VERSION_STRING_READ(10),
- BOARD_PART_ID_SERIAL_NUMBER_READ(11),
- SET_SAMPLE_RATE(12),
- SET_FREQUENCY(13),
- SET_LNA_GAIN(14),
- SET_MIXER_GAIN(15),
- SET_VGA_GAIN(16),
- SET_LNA_AGC(17),
- SET_MIXER_AGC(18),
- MS_VENDOR_COMMAND(19),
- SET_RF_BIAS_COMMAND(20),
- GPIO_WRITE(21),
- GPIO_READ(22),
- GPIO_DIR__WRITE(23),
- GPIO_DIR_READ(24),
- GET_SAMPLE_RATES(25),
- SET_PACKING(26);
-
- private int mValue;
-
- Command(int value)
- {
- mValue = value;
- }
-
- public byte getValue()
- {
- return (byte) mValue;
- }
-
- public static Command fromValue(int value)
- {
- if(0 <= value && value <= 25)
- {
- return Command.values()[value];
- }
-
- return INVALID;
- }
- }
-
- public enum ReceiverMode
- {
- OFF(0),
- ON(1);
-
- private int mValue;
-
- private ReceiverMode(int value)
- {
- mValue = value;
- }
-
- public int getValue()
- {
- return mValue;
- }
- }
-
- /**
- * General Purpose Input/Output Ports (accessible on the airspy board)
- */
- public enum GPIOPort
- {
- PORT_0(0),
- PORT_1(1),
- PORT_2(2),
- PORT_3(3),
- PORT_4(4),
- PORT_5(5),
- PORT_6(6),
- PORT_7(7);
-
- private int mValue;
-
- private GPIOPort(int value)
- {
- mValue = value;
- }
-
- public int getValue()
- {
- return mValue;
- }
- }
-
- /**
- * General Purpose Input/Output Pins (accessible on the airspy board)
- */
- public enum GPIOPin
- {
- PIN_0(0),
- PIN_1(1),
- PIN_2(2),
- PIN_3(3),
- PIN_4(4),
- PIN_5(5),
- PIN_6(6),
- PIN_7(7),
- PIN_8(8),
- PIN_9(9),
- PIN_10(10),
- PIN_11(11),
- PIN_12(12),
- PIN_13(13),
- PIN_14(14),
- PIN_15(15),
- PIN_16(16),
- PIN_17(17),
- PIN_18(18),
- PIN_19(19),
- PIN_20(20),
- PIN_21(21),
- PIN_22(22),
- PIN_23(23),
- PIN_24(24),
- PIN_25(25),
- PIN_26(26),
- PIN_27(27),
- PIN_28(28),
- PIN_29(29),
- PIN_30(30),
- PIN_31(31);
-
- private int mValue;
-
- GPIOPin(int value)
- {
- mValue = value;
- }
-
- public int getValue()
- {
- return mValue;
- }
- }
-}
\ No newline at end of file
+}
diff --git a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerEditor.java b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerEditor.java
index ed32e804..fc684e3b 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerEditor.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerEditor.java
@@ -689,8 +689,8 @@ public class AirspyTunerEditor extends TunerEditor");
- sb.append("Board ID: ");
- sb.append(info.getBoardID().getLabel());
+ sb.append("Board: ");
+ sb.append(info.getBoardName());
sb.append("
");
return sb.toString();
diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java
index dabd85e9..2a76d59a 100644
--- a/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java
+++ b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java
@@ -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());
diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationType.java b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationType.java
index f8f97192..94959763 100644
--- a/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationType.java
+++ b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationType.java
@@ -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);
diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/sample/PackedSampleConverterCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/sample/PackedSampleConverterCalibration.java
deleted file mode 100644
index 987dad35..00000000
--- a/src/main/java/io/github/dsheirer/vector/calibrate/sample/PackedSampleConverterCalibration.java
+++ /dev/null
@@ -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
- * ****************************************************************************
- */
-
-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);
- }
-}
diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/sample/UnpackedInterleavedSampleConverterCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/sample/UnpackedInterleavedSampleConverterCalibration.java
deleted file mode 100644
index 0cc2f947..00000000
--- a/src/main/java/io/github/dsheirer/vector/calibrate/sample/UnpackedInterleavedSampleConverterCalibration.java
+++ /dev/null
@@ -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
- * ****************************************************************************
- */
-
-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);
- }
-}
diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/sample/UnpackedSampleConverterCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/sample/UnpackedSampleConverterCalibration.java
deleted file mode 100644
index 822d703e..00000000
--- a/src/main/java/io/github/dsheirer/vector/calibrate/sample/UnpackedSampleConverterCalibration.java
+++ /dev/null
@@ -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
- * ****************************************************************************
- */
-
-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);
- }
-}