diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index f3a238a5..5eebd232 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -37,11 +37,12 @@ jobs:
echo -e "ref: $GITHUB_REF \ncommit: $GITHUB_SHA\nbuild: $(date +"%Y-%m-%dT%H:%M:%SZ")" > build/image/build_info.txt
- name: Add build artifacts to nightly release
- uses: pyTooling/Actions/releaser@main
+ uses: softprops/action-gh-release@v2
with:
- tag: nightly
- rm: false
- token: ${{ secrets.GITHUB_TOKEN }}
+ tag_name: nightly
+ prerelease: true
files: |
build/image/*.zip
build/image/build_info.txt
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/hf/AirspyHfNativeBuffer.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyHfNativeBuffer.java
similarity index 97%
rename from src/main/java/io/github/dsheirer/buffer/airspy/hf/AirspyHfNativeBuffer.java
rename to src/main/java/io/github/dsheirer/buffer/airspy/AirspyHfNativeBuffer.java
index 476a59dc..ae34f952 100644
--- a/src/main/java/io/github/dsheirer/buffer/airspy/hf/AirspyHfNativeBuffer.java
+++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyHfNativeBuffer.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2023 Dennis Sheirer
+ * 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
@@ -17,7 +17,7 @@
* ****************************************************************************
*/
-package io.github.dsheirer.buffer.airspy.hf;
+package io.github.dsheirer.buffer.airspy;
import io.github.dsheirer.buffer.AbstractNativeBuffer;
import io.github.dsheirer.sample.complex.ComplexSamples;
diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/hf/AirspyHfNativeBufferFactory.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyHfNativeBufferFactory.java
similarity index 96%
rename from src/main/java/io/github/dsheirer/buffer/airspy/hf/AirspyHfNativeBufferFactory.java
rename to src/main/java/io/github/dsheirer/buffer/airspy/AirspyHfNativeBufferFactory.java
index 8bb2f80a..789bef1a 100644
--- a/src/main/java/io/github/dsheirer/buffer/airspy/hf/AirspyHfNativeBufferFactory.java
+++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyHfNativeBufferFactory.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2023 Dennis Sheirer
+ * 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
@@ -17,7 +17,7 @@
* ****************************************************************************
*/
-package io.github.dsheirer.buffer.airspy.hf;
+package io.github.dsheirer.buffer.airspy;
import io.github.dsheirer.buffer.AbstractNativeBufferFactory;
import io.github.dsheirer.buffer.DcCorrectionManager;
diff --git a/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIterator.java b/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIterator.java
new file mode 100644
index 00000000..8bba8e5a
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIterator.java
@@ -0,0 +1,119 @@
+/*
+ * *****************************************************************************
+ * 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.hydrasdr;
+
+import io.github.dsheirer.dsp.filter.hilbert.HilbertTransform;
+import java.util.Iterator;
+
+/**
+ * Base complex samples iterator for raw HydraSDR sample buffers. Incorporates a Hilbert transform filter
+ * for converting the real sample array to complex samples.
+ * @param either ComplexSamples or InterleavedComplexSamples
+ */
+public abstract class HydraSdrBufferIterator 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 HydraSDR.
+ * @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 HydraSdrBufferIterator(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/hydrasdr/HydraSdrBufferIteratorScalar.java b/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIteratorScalar.java
new file mode 100644
index 00000000..2594f107
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIteratorScalar.java
@@ -0,0 +1,98 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.buffer.hydrasdr;
+
+import io.github.dsheirer.sample.complex.ComplexSamples;
+import java.util.Arrays;
+
+/**
+ * Scalar implementation for non-packed HydraSDR native buffers
+ */
+public class HydraSdrBufferIteratorScalar extends HydraSdrBufferIterator
+{
+ /**
+ * Constructs an instance
+ *
+ * @param samples from the HydraSDR, 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 HydraSdrBufferIteratorScalar(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/hydrasdr/HydraSdrBufferIteratorVector128Bits.java b/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIteratorVector128Bits.java
new file mode 100644
index 00000000..fe2fe1c7
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIteratorVector128Bits.java
@@ -0,0 +1,128 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.buffer.hydrasdr;
+
+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 HydraSDR native buffers
+ */
+public class HydraSdrBufferIteratorVector128Bits extends HydraSdrBufferIterator
+{
+ private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128;
+
+ /**
+ * Constructs an instance
+ *
+ * @param samples from the HydraSDR, 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 HydraSdrBufferIteratorVector128Bits(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/hydrasdr/HydraSdrBufferIteratorVector256Bits.java b/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIteratorVector256Bits.java
new file mode 100644
index 00000000..bf828911
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIteratorVector256Bits.java
@@ -0,0 +1,122 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.buffer.hydrasdr;
+
+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 HydraSDR native buffers
+ */
+public class HydraSdrBufferIteratorVector256Bits extends HydraSdrBufferIterator
+{
+ private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256;
+
+ /**
+ * Constructs an instance
+ *
+ * @param samples from the HydraSDR, 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 HydraSdrBufferIteratorVector256Bits(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/hydrasdr/HydraSdrBufferIteratorVector512Bits.java b/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIteratorVector512Bits.java
new file mode 100644
index 00000000..bfd7e7a1
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIteratorVector512Bits.java
@@ -0,0 +1,125 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.buffer.hydrasdr;
+
+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 HydraSDR native buffers
+ */
+public class HydraSdrBufferIteratorVector512Bits extends HydraSdrBufferIterator
+{
+ private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_512;
+ private float[] mFilterPart2;
+
+ /**
+ * Constructs an instance
+ *
+ * @param samples from the HydraSDR, 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 HydraSdrBufferIteratorVector512Bits(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/hydrasdr/HydraSdrBufferIteratorVector64Bits.java b/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIteratorVector64Bits.java
new file mode 100644
index 00000000..75454525
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIteratorVector64Bits.java
@@ -0,0 +1,141 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.buffer.hydrasdr;
+
+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 HydraSDR native buffers
+ */
+public class HydraSdrBufferIteratorVector64Bits extends HydraSdrBufferIterator
+{
+ private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64;
+
+ /**
+ * Constructs an instance
+ *
+ * @param samples from the HydraSDR, 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 HydraSdrBufferIteratorVector64Bits(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/hydrasdr/HydraSdrInterleavedBufferIteratorScalar.java b/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrInterleavedBufferIteratorScalar.java
new file mode 100644
index 00000000..c484d1e4
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrInterleavedBufferIteratorScalar.java
@@ -0,0 +1,96 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.buffer.hydrasdr;
+
+import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
+
+/**
+ * Scalar implementation for non-packed HydraSDR native buffers
+ */
+public class HydraSdrInterleavedBufferIteratorScalar extends HydraSdrBufferIterator
+{
+ /**
+ * Constructs an instance
+ *
+ * @param samples from the HydraSDR, 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 HydraSdrInterleavedBufferIteratorScalar(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/hydrasdr/HydraSdrInterleavedBufferIteratorVector128Bits.java b/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrInterleavedBufferIteratorVector128Bits.java
new file mode 100644
index 00000000..2033a6b7
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrInterleavedBufferIteratorVector128Bits.java
@@ -0,0 +1,125 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.buffer.hydrasdr;
+
+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 HydraSDR native buffers
+ */
+public class HydraSdrInterleavedBufferIteratorVector128Bits extends HydraSdrBufferIterator
+{
+ private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128;
+
+ /**
+ * Constructs an instance
+ *
+ * @param samples from the HydraSDR, 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 HydraSdrInterleavedBufferIteratorVector128Bits(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/hydrasdr/HydraSdrInterleavedBufferIteratorVector256Bits.java b/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrInterleavedBufferIteratorVector256Bits.java
new file mode 100644
index 00000000..742c361b
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrInterleavedBufferIteratorVector256Bits.java
@@ -0,0 +1,119 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.buffer.hydrasdr;
+
+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 HydraSDR native buffers
+ */
+public class HydraSdrInterleavedBufferIteratorVector256Bits extends HydraSdrBufferIterator
+{
+ private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256;
+
+ /**
+ * Constructs an instance
+ *
+ * @param samples from the HydraSDR, 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 HydraSdrInterleavedBufferIteratorVector256Bits(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/hydrasdr/HydraSdrInterleavedBufferIteratorVector512Bits.java b/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrInterleavedBufferIteratorVector512Bits.java
new file mode 100644
index 00000000..39dc36dc
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrInterleavedBufferIteratorVector512Bits.java
@@ -0,0 +1,122 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.buffer.hydrasdr;
+
+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 HydraSDR native buffers
+ */
+public class HydraSdrInterleavedBufferIteratorVector512Bits extends HydraSdrBufferIterator
+{
+ private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_512;
+ private float[] mFilterPart2;
+
+ /**
+ * Constructs an instance
+ *
+ * @param samples from the HydraSDR, 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 HydraSdrInterleavedBufferIteratorVector512Bits(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/hydrasdr/HydraSdrInterleavedBufferIteratorVector64Bits.java b/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrInterleavedBufferIteratorVector64Bits.java
new file mode 100644
index 00000000..048465b1
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrInterleavedBufferIteratorVector64Bits.java
@@ -0,0 +1,137 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.buffer.hydrasdr;
+
+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 HydraSDR native buffers
+ */
+public class HydraSdrInterleavedBufferIteratorVector64Bits extends HydraSdrBufferIterator
+{
+ private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64;
+
+ /**
+ * Constructs an instance
+ *
+ * @param samples from the HydraSDR, 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 HydraSdrInterleavedBufferIteratorVector64Bits(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/airspy/IAirspySampleConverter.java b/src/main/java/io/github/dsheirer/buffer/hydrasdr/IHydraSdrSampleConverter.java
similarity index 77%
rename from src/main/java/io/github/dsheirer/buffer/airspy/IAirspySampleConverter.java
rename to src/main/java/io/github/dsheirer/buffer/hydrasdr/IHydraSdrSampleConverter.java
index 8164f37c..771f559b 100644
--- a/src/main/java/io/github/dsheirer/buffer/airspy/IAirspySampleConverter.java
+++ b/src/main/java/io/github/dsheirer/buffer/hydrasdr/IHydraSdrSampleConverter.java
@@ -17,18 +17,18 @@
* ****************************************************************************
*/
-package io.github.dsheirer.buffer.airspy;
+package io.github.dsheirer.buffer.hydrasdr;
import java.nio.ByteBuffer;
/**
- * Interface to convert airspy samples from a byte buffer to an array of shorts.
+ * Interface to convert HydraSDR samples from a byte buffer to an array of shorts.
*/
-public interface IAirspySampleConverter
+public interface IHydraSdrSampleConverter
{
/**
- * Converts the airspy byte samples contained in the byte buffer to their short-valued representation
- * @param buffer of airspy samples
+ * Converts the HydraSDR byte samples contained in the byte buffer to their short-valued representation
+ * @param buffer of HydraSDR samples
* @return converted samples
*/
short[] convert(ByteBuffer buffer);
diff --git a/src/main/java/io/github/dsheirer/buffer/hydrasdr/ScalarPackedSampleConverter.java b/src/main/java/io/github/dsheirer/buffer/hydrasdr/ScalarPackedSampleConverter.java
new file mode 100644
index 00000000..04d40d0b
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/buffer/hydrasdr/ScalarPackedSampleConverter.java
@@ -0,0 +1,94 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.buffer.hydrasdr;
+
+import io.github.dsheirer.buffer.DcCorrectionManager;
+import java.nio.ByteBuffer;
+
+/**
+ * Scalar implementation of HydraSDR sample converter for packed samples.
+ */
+public class ScalarPackedSampleConverter implements IHydraSdrSampleConverter
+{
+ /**
+ * 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 *= HydraSdrBufferIterator.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();
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/ScalarUnpackedSampleConverter.java b/src/main/java/io/github/dsheirer/buffer/hydrasdr/ScalarUnpackedSampleConverter.java
similarity index 89%
rename from src/main/java/io/github/dsheirer/buffer/airspy/ScalarUnpackedSampleConverter.java
rename to src/main/java/io/github/dsheirer/buffer/hydrasdr/ScalarUnpackedSampleConverter.java
index 6c702c97..6c0fc1fa 100644
--- a/src/main/java/io/github/dsheirer/buffer/airspy/ScalarUnpackedSampleConverter.java
+++ b/src/main/java/io/github/dsheirer/buffer/hydrasdr/ScalarUnpackedSampleConverter.java
@@ -17,15 +17,15 @@
* ****************************************************************************
*/
-package io.github.dsheirer.buffer.airspy;
+package io.github.dsheirer.buffer.hydrasdr;
import io.github.dsheirer.buffer.DcCorrectionManager;
import java.nio.ByteBuffer;
/**
- * Scalar implementation of airspy sample converter for un-packed samples.
+ * Scalar implementation of HydraSDR sample converter for un-packed samples.
*/
-public class ScalarUnpackedSampleConverter implements IAirspySampleConverter
+public class ScalarUnpackedSampleConverter implements IHydraSdrSampleConverter
{
/**
* Manages DC calculations and processing interval
@@ -58,7 +58,7 @@ public class ScalarUnpackedSampleConverter implements IAirspySampleConverter
}
float averageDcNow = ((float)dcAccumulator / (float)samples.length) - 2048.0f;
- averageDcNow *= AirspyBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT;
+ averageDcNow *= HydraSdrBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT;
mDcCalculationManager.adjust(averageDcNow);
}
else
diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/VectorUnpackedSampleConverter.java b/src/main/java/io/github/dsheirer/buffer/hydrasdr/VectorUnpackedSampleConverter.java
similarity index 93%
rename from src/main/java/io/github/dsheirer/buffer/airspy/VectorUnpackedSampleConverter.java
rename to src/main/java/io/github/dsheirer/buffer/hydrasdr/VectorUnpackedSampleConverter.java
index 45b889c7..c0a6cc34 100644
--- a/src/main/java/io/github/dsheirer/buffer/airspy/VectorUnpackedSampleConverter.java
+++ b/src/main/java/io/github/dsheirer/buffer/hydrasdr/VectorUnpackedSampleConverter.java
@@ -17,7 +17,7 @@
* ****************************************************************************
*/
-package io.github.dsheirer.buffer.airspy;
+package io.github.dsheirer.buffer.hydrasdr;
import io.github.dsheirer.buffer.DcCorrectionManager;
import java.nio.ByteBuffer;
@@ -26,9 +26,9 @@ import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
- * Scalar implementation of airspy sample converter for un-packed samples.
+ * Vector implementation of HydraSDR sample converter for un-packed samples.
*/
-public class VectorUnpackedSampleConverter implements IAirspySampleConverter
+public class VectorUnpackedSampleConverter implements IHydraSdrSampleConverter
{
private static final VectorSpecies VECTOR_SPECIES = ShortVector.SPECIES_PREFERRED;
@@ -88,7 +88,7 @@ public class VectorUnpackedSampleConverter implements IAirspySampleConverter
}
float averageDcNow = ((float)dcAccumulator / (float)samples.length) - 2048.0f;
- averageDcNow *= AirspyBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT;
+ averageDcNow *= HydraSdrBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT;
mDcCalculationManager.adjust(averageDcNow);
}
else
diff --git a/src/main/java/io/github/dsheirer/buffer/sample/ISampleConverter.java b/src/main/java/io/github/dsheirer/buffer/sample/ISampleConverter.java
new file mode 100644
index 00000000..906f9144
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/buffer/sample/ISampleConverter.java
@@ -0,0 +1,40 @@
+/*
+ * *****************************************************************************
+ * 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/airspy/AirspyBufferIterator.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIterator.java
similarity index 93%
rename from src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIterator.java
rename to src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIterator.java
index cc4fc807..9567aeab 100644
--- a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIterator.java
+++ b/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIterator.java
@@ -17,17 +17,17 @@
* ****************************************************************************
*/
-package io.github.dsheirer.buffer.airspy;
+package io.github.dsheirer.buffer.sample;
import io.github.dsheirer.dsp.filter.hilbert.HilbertTransform;
import java.util.Iterator;
/**
- * Base complex samples iterator for raw Airspy sample buffers. Incorporates a Hilbert transform filter
+ * 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 AirspyBufferIterator implements Iterator
+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;
@@ -47,14 +47,14 @@ public abstract class AirspyBufferIterator implements Iterator
/**
* Constructs an instance
- * @param samples from the airspy.
+ * @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 AirspyBufferIterator(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp,
+ public SampleBufferIterator(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp,
float samplesPerMillisecond)
{
if(residualI.length != I_OVERLAP || residualQ.length != Q_OVERLAP)
diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorScalar.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorScalar.java
similarity index 90%
rename from src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorScalar.java
rename to src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorScalar.java
index d2f60ab4..383af553 100644
--- a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorScalar.java
+++ b/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorScalar.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * 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
@@ -17,27 +17,27 @@
* ****************************************************************************
*/
-package io.github.dsheirer.buffer.airspy;
+package io.github.dsheirer.buffer.sample;
import io.github.dsheirer.sample.complex.ComplexSamples;
import java.util.Arrays;
/**
- * Scalar implementation for non-packed Airspy native buffers
+ * Scalar implementation for non-packed native buffers
*/
-public class AirspyBufferIteratorScalar extends AirspyBufferIterator
+public class SampleBufferIteratorScalar extends SampleBufferIterator
{
/**
* Constructs an instance
*
- * @param samples from the airspy, either packed or unpacked.
+ * @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 AirspyBufferIteratorScalar(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp, float samplesPerMillisecond)
+ public SampleBufferIteratorScalar(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
}
diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector128Bits.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector128Bits.java
similarity index 93%
rename from src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector128Bits.java
rename to src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector128Bits.java
index beef74f2..29a8a4b2 100644
--- a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector128Bits.java
+++ b/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector128Bits.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * 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
@@ -17,7 +17,7 @@
* ****************************************************************************
*/
-package io.github.dsheirer.buffer.airspy;
+package io.github.dsheirer.buffer.sample;
import io.github.dsheirer.sample.complex.ComplexSamples;
import java.util.Arrays;
@@ -26,23 +26,23 @@ import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
- * Vector SIMD implementation for non-packed Airspy native buffers
+ * Vector SIMD implementation for non-packed native buffers
*/
-public class AirspyBufferIteratorVector128Bits extends AirspyBufferIterator
+public class SampleBufferIteratorVector128Bits extends SampleBufferIterator
{
private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128;
/**
* Constructs an instance
*
- * @param samples from the airspy, either packed or unpacked.
+ * @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 AirspyBufferIteratorVector128Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
+ public SampleBufferIteratorVector128Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector256Bits.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector256Bits.java
similarity index 93%
rename from src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector256Bits.java
rename to src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector256Bits.java
index 3300c0be..9280c4cc 100644
--- a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector256Bits.java
+++ b/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector256Bits.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * 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
@@ -17,7 +17,7 @@
* ****************************************************************************
*/
-package io.github.dsheirer.buffer.airspy;
+package io.github.dsheirer.buffer.sample;
import io.github.dsheirer.sample.complex.ComplexSamples;
import java.util.Arrays;
@@ -26,23 +26,23 @@ import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
- * Vector SIMD implementation for non-packed Airspy native buffers
+ * Vector SIMD implementation for non-packed native buffers
*/
-public class AirspyBufferIteratorVector256Bits extends AirspyBufferIterator
+public class SampleBufferIteratorVector256Bits extends SampleBufferIterator
{
private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256;
/**
* Constructs an instance
*
- * @param samples from the airspy, either packed or unpacked.
+ * @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 AirspyBufferIteratorVector256Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
+ public SampleBufferIteratorVector256Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector512Bits.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector512Bits.java
similarity index 93%
rename from src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector512Bits.java
rename to src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector512Bits.java
index 391de381..ee7f844f 100644
--- a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector512Bits.java
+++ b/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector512Bits.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * 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
@@ -17,7 +17,7 @@
* ****************************************************************************
*/
-package io.github.dsheirer.buffer.airspy;
+package io.github.dsheirer.buffer.sample;
import io.github.dsheirer.sample.complex.ComplexSamples;
import java.util.Arrays;
@@ -26,9 +26,9 @@ import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
- * Vector SIMD implementation for non-packed Airspy native buffers
+ * Vector SIMD implementation for non-packed native buffers
*/
-public class AirspyBufferIteratorVector512Bits extends AirspyBufferIterator
+public class SampleBufferIteratorVector512Bits extends SampleBufferIterator
{
private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_512;
private float[] mFilterPart2;
@@ -36,14 +36,14 @@ public class AirspyBufferIteratorVector512Bits extends AirspyBufferIterator
+public class SampleBufferIteratorVector64Bits extends SampleBufferIterator
{
private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64;
/**
* Constructs an instance
*
- * @param samples from the airspy, either packed or unpacked.
+ * @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 AirspyBufferIteratorVector64Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
+ public SampleBufferIteratorVector64Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorScalar.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorScalar.java
similarity index 88%
rename from src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorScalar.java
rename to src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorScalar.java
index 87b13c0c..0540d626 100644
--- a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorScalar.java
+++ b/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorScalar.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * 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
@@ -17,26 +17,26 @@
* ****************************************************************************
*/
-package io.github.dsheirer.buffer.airspy;
+package io.github.dsheirer.buffer.sample;
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
/**
- * Scalar implementation for non-packed Airspy native buffers
+ * Scalar implementation for non-packed native buffers
*/
-public class AirspyInterleavedBufferIteratorScalar extends AirspyBufferIterator
+public class SampleInterleavedBufferIteratorScalar extends SampleBufferIterator
{
/**
* Constructs an instance
*
- * @param samples from the airspy, either packed or unpacked.
+ * @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 AirspyInterleavedBufferIteratorScalar(short[] samples, short[] residualI, short[] residualQ, float averageDc,
+ public SampleInterleavedBufferIteratorScalar(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector128Bits.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector128Bits.java
similarity index 92%
rename from src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector128Bits.java
rename to src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector128Bits.java
index 83af4966..205656e2 100644
--- a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector128Bits.java
+++ b/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector128Bits.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * 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
@@ -17,7 +17,7 @@
* ****************************************************************************
*/
-package io.github.dsheirer.buffer.airspy;
+package io.github.dsheirer.buffer.sample;
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
import jdk.incubator.vector.FloatVector;
@@ -25,23 +25,23 @@ import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
- * Vector SIMD implementation for non-packed Airspy native buffers
+ * Vector SIMD implementation for non-packed native buffers
*/
-public class AirspyInterleavedBufferIteratorVector128Bits extends AirspyBufferIterator
+public class SampleInterleavedBufferIteratorVector128Bits extends SampleBufferIterator
{
private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128;
/**
* Constructs an instance
*
- * @param samples from the airspy, either packed or unpacked.
+ * @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 AirspyInterleavedBufferIteratorVector128Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
+ public SampleInterleavedBufferIteratorVector128Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector256Bits.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector256Bits.java
similarity index 92%
rename from src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector256Bits.java
rename to src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector256Bits.java
index 9553f0ad..3951472a 100644
--- a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector256Bits.java
+++ b/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector256Bits.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * 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
@@ -17,7 +17,7 @@
* ****************************************************************************
*/
-package io.github.dsheirer.buffer.airspy;
+package io.github.dsheirer.buffer.sample;
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
import jdk.incubator.vector.FloatVector;
@@ -25,23 +25,23 @@ import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
- * Vector SIMD implementation for non-packed Airspy native buffers
+ * Vector SIMD implementation for non-packed native buffers
*/
-public class AirspyInterleavedBufferIteratorVector256Bits extends AirspyBufferIterator
+public class SampleInterleavedBufferIteratorVector256Bits extends SampleBufferIterator
{
private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256;
/**
* Constructs an instance
*
- * @param samples from the airspy, either packed or unpacked.
+ * @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 AirspyInterleavedBufferIteratorVector256Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
+ public SampleInterleavedBufferIteratorVector256Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector512Bits.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector512Bits.java
similarity index 92%
rename from src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector512Bits.java
rename to src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector512Bits.java
index 45cf4c89..cf6df573 100644
--- a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector512Bits.java
+++ b/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector512Bits.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * 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
@@ -17,7 +17,7 @@
* ****************************************************************************
*/
-package io.github.dsheirer.buffer.airspy;
+package io.github.dsheirer.buffer.sample;
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
import jdk.incubator.vector.FloatVector;
@@ -25,9 +25,9 @@ import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
- * Vector SIMD implementation for non-packed Airspy native buffers
+ * Vector SIMD implementation for non-packed native buffers
*/
-public class AirspyInterleavedBufferIteratorVector512Bits extends AirspyBufferIterator
+public class SampleInterleavedBufferIteratorVector512Bits extends SampleBufferIterator
{
private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_512;
private float[] mFilterPart2;
@@ -35,14 +35,14 @@ public class AirspyInterleavedBufferIteratorVector512Bits extends AirspyBufferIt
/**
* Constructs an instance
*
- * @param samples from the airspy, either packed or unpacked.
+ * @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 AirspyInterleavedBufferIteratorVector512Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
+ public SampleInterleavedBufferIteratorVector512Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector64Bits.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector64Bits.java
similarity index 94%
rename from src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector64Bits.java
rename to src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector64Bits.java
index a2870c75..8859565a 100644
--- a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector64Bits.java
+++ b/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector64Bits.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * 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
@@ -17,7 +17,7 @@
* ****************************************************************************
*/
-package io.github.dsheirer.buffer.airspy;
+package io.github.dsheirer.buffer.sample;
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
import jdk.incubator.vector.FloatVector;
@@ -25,23 +25,23 @@ import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
- * Vector SIMD implementation for non-packed Airspy native buffers
+ * Vector SIMD implementation for non-packed native buffers
*/
-public class AirspyInterleavedBufferIteratorVector64Bits extends AirspyBufferIterator
+public class SampleInterleavedBufferIteratorVector64Bits extends SampleBufferIterator
{
private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64;
/**
* Constructs an instance
*
- * @param samples from the airspy, either packed or unpacked.
+ * @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 AirspyInterleavedBufferIteratorVector64Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
+ public SampleInterleavedBufferIteratorVector64Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyNativeBuffer.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleNativeBuffer.java
similarity index 78%
rename from src/main/java/io/github/dsheirer/buffer/airspy/AirspyNativeBuffer.java
rename to src/main/java/io/github/dsheirer/buffer/sample/SampleNativeBuffer.java
index dbcbf7bc..05c95db4 100644
--- a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyNativeBuffer.java
+++ b/src/main/java/io/github/dsheirer/buffer/sample/SampleNativeBuffer.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * 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
@@ -17,7 +17,7 @@
* ****************************************************************************
*/
-package io.github.dsheirer.buffer.airspy;
+package io.github.dsheirer.buffer.sample;
import io.github.dsheirer.buffer.AbstractNativeBuffer;
import io.github.dsheirer.sample.complex.ComplexSamples;
@@ -26,9 +26,9 @@ import io.github.dsheirer.vector.calibrate.Implementation;
import java.util.Iterator;
/**
- * Native buffer scalar implementation for Airspy non-packed samples.
+ * Native buffer scalar implementation for non-packed samples.
*/
-public class AirspyNativeBuffer extends AbstractNativeBuffer
+public class SampleNativeBuffer extends AbstractNativeBuffer
{
private short[] mSamples;
private short[] mResidualI;
@@ -39,7 +39,7 @@ public class AirspyNativeBuffer extends AbstractNativeBuffer
/**
* Constructs an instance
- * @param samples (non-packed) from the airspy device
+ * @param samples (non-packed) from the device
* @param residualI samples from previous buffer
* @param residualQ samples from previous buffer
* @param averageDc measured
@@ -48,17 +48,17 @@ public class AirspyNativeBuffer extends AbstractNativeBuffer
* @param nonInterleavedImplementation optimal, scalar vs vector SIMD
* @param samplesPerMillisecond used to calculate sub-buffer fragment timestamp offsets from the start of this buffer.
*/
- public AirspyNativeBuffer(short[] samples, short[] residualI, short[] residualQ, float averageDc,
+ 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 % AirspyBufferIterator.FRAGMENT_SIZE != 0)
+ if(samples.length % SampleBufferIterator.FRAGMENT_SIZE != 0)
{
throw new IllegalArgumentException("Samples short[] length [" + samples.length +
- "] must be an even multiple of " + AirspyBufferIterator.FRAGMENT_SIZE);
+ "] must be an even multiple of " + SampleBufferIterator.FRAGMENT_SIZE);
}
mSamples = samples;
@@ -80,11 +80,11 @@ public class AirspyNativeBuffer extends AbstractNativeBuffer
{
return switch(mInterleavedImplementation)
{
- case VECTOR_SIMD_512 -> new AirspyBufferIteratorVector512Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
- case VECTOR_SIMD_256-> new AirspyBufferIteratorVector256Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
- case VECTOR_SIMD_128-> new AirspyBufferIteratorVector128Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
- case VECTOR_SIMD_64 ->new AirspyBufferIteratorVector64Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
- default -> new AirspyBufferIteratorScalar(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
+ 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());
};
}
@@ -93,11 +93,11 @@ public class AirspyNativeBuffer extends AbstractNativeBuffer
{
return switch(mInterleavedImplementation)
{
- case VECTOR_SIMD_512 -> new AirspyInterleavedBufferIteratorVector512Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
- case VECTOR_SIMD_256-> new AirspyInterleavedBufferIteratorVector256Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
- case VECTOR_SIMD_128-> new AirspyInterleavedBufferIteratorVector128Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
- case VECTOR_SIMD_64 ->new AirspyInterleavedBufferIteratorVector64Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
- default -> new AirspyInterleavedBufferIteratorScalar(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
+ 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/airspy/AirspyNativeBufferFactory.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleNativeBufferFactory.java
similarity index 81%
rename from src/main/java/io/github/dsheirer/buffer/airspy/AirspyNativeBufferFactory.java
rename to src/main/java/io/github/dsheirer/buffer/sample/SampleNativeBufferFactory.java
index 03552407..0efea14c 100644
--- a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyNativeBufferFactory.java
+++ b/src/main/java/io/github/dsheirer/buffer/sample/SampleNativeBufferFactory.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * 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
@@ -17,7 +17,7 @@
* ****************************************************************************
*/
-package io.github.dsheirer.buffer.airspy;
+package io.github.dsheirer.buffer.sample;
import io.github.dsheirer.buffer.AbstractNativeBufferFactory;
import io.github.dsheirer.buffer.INativeBuffer;
@@ -30,26 +30,26 @@ import java.util.Arrays;
/**
* Implements a factory for creating SignedByteNativeBuffer instances
*/
-public class AirspyNativeBufferFactory extends AbstractNativeBufferFactory
+public class SampleNativeBufferFactory extends AbstractNativeBufferFactory
{
private boolean mSamplePacking = false;
- private short[] mResidualI = new short[AirspyBufferIterator.I_OVERLAP];
- private short[] mResidualQ = new short[AirspyBufferIterator.Q_OVERLAP];
- private IAirspySampleConverter mConverter;
+ 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 AirspyNativeBufferFactory()
+ public SampleNativeBufferFactory()
{
updateConverter();
mInterleavedIteratorImplementation = CalibrationManager.getInstance()
- .getImplementation(CalibrationType.AIRSPY_UNPACKED_INTERLEAVED_ITERATOR);
+ .getImplementation(CalibrationType.SAMPLE_UNPACKED_INTERLEAVED_ITERATOR);
mNonInterleavedIteratorImplementation = CalibrationManager.getInstance()
- .getImplementation(CalibrationType.AIRSPY_UNPACKED_ITERATOR);
+ .getImplementation(CalibrationType.SAMPLE_UNPACKED_ITERATOR);
}
/**
@@ -78,7 +78,7 @@ public class AirspyNativeBufferFactory extends AbstractNativeBufferFactory
}
else
{
- Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.AIRSPY_SAMPLE_CONVERTER);
+ Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.SAMPLE_PACKED_CONVERTER);
if(implementation == Implementation.VECTOR_SIMD_PREFERRED)
{
@@ -102,7 +102,7 @@ public class AirspyNativeBufferFactory extends AbstractNativeBufferFactory
{
short[] samples = mConverter.convert(buffer);
- INativeBuffer nativeBuffer = new AirspyNativeBuffer(samples,
+ INativeBuffer nativeBuffer = new SampleNativeBuffer(samples,
Arrays.copyOf(mResidualI, mResidualI.length),
Arrays.copyOf(mResidualQ, mResidualQ.length), mConverter.getAverageDc(), timestamp,
mInterleavedIteratorImplementation, mNonInterleavedIteratorImplementation, getSamplesPerMillisecond());
@@ -118,16 +118,16 @@ public class AirspyNativeBufferFactory extends AbstractNativeBufferFactory
*/
private void extractResidual(short[] samples)
{
- int offset = samples.length - (AirspyBufferIterator.I_OVERLAP * 2);
+ int offset = samples.length - (SampleBufferIterator.I_OVERLAP * 2);
- for(int i = 0; i < AirspyBufferIterator.I_OVERLAP; i++)
+ for(int i = 0; i < SampleBufferIterator.I_OVERLAP; i++)
{
mResidualI[i] = samples[offset + (2 * i)];
}
- offset = samples.length - (AirspyBufferIterator.Q_OVERLAP * 2) + 1;
+ offset = samples.length - (SampleBufferIterator.Q_OVERLAP * 2) + 1;
- for(int q = 0; q < AirspyBufferIterator.Q_OVERLAP; q++)
+ 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/airspy/ScalarPackedSampleConverter.java b/src/main/java/io/github/dsheirer/buffer/sample/ScalarPackedSampleConverter.java
similarity index 92%
rename from src/main/java/io/github/dsheirer/buffer/airspy/ScalarPackedSampleConverter.java
rename to src/main/java/io/github/dsheirer/buffer/sample/ScalarPackedSampleConverter.java
index 9648515f..7bcd3a7c 100644
--- a/src/main/java/io/github/dsheirer/buffer/airspy/ScalarPackedSampleConverter.java
+++ b/src/main/java/io/github/dsheirer/buffer/sample/ScalarPackedSampleConverter.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * 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
@@ -17,15 +17,15 @@
* ****************************************************************************
*/
-package io.github.dsheirer.buffer.airspy;
+package io.github.dsheirer.buffer.sample;
import io.github.dsheirer.buffer.DcCorrectionManager;
import java.nio.ByteBuffer;
/**
- * Scalar implementation of airspy sample converter for un-packed samples.
+ * Scalar implementation of sample converter for un-packed samples.
*/
-public class ScalarPackedSampleConverter implements IAirspySampleConverter
+public class ScalarPackedSampleConverter implements ISampleConverter
{
/**
* Manages DC calculations and processing interval
@@ -64,7 +64,7 @@ public class ScalarPackedSampleConverter implements IAirspySampleConverter
}
float averageDcNow = ((float)dcAccumulator / (float)samples.length) - 2048.0f;
- averageDcNow *= AirspyBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT;
+ averageDcNow *= SampleBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT;
mDcCalculationManager.adjust(averageDcNow);
}
else
diff --git a/src/main/java/io/github/dsheirer/buffer/sample/ScalarUnpackedSampleConverter.java b/src/main/java/io/github/dsheirer/buffer/sample/ScalarUnpackedSampleConverter.java
new file mode 100644
index 00000000..50ad184f
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/buffer/sample/ScalarUnpackedSampleConverter.java
@@ -0,0 +1,83 @@
+/*
+ * *****************************************************************************
+ * 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
new file mode 100644
index 00000000..9c018207
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/buffer/sample/VectorUnpackedSampleConverter.java
@@ -0,0 +1,129 @@
+/*
+ * *****************************************************************************
+ * 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/TunerClass.java b/src/main/java/io/github/dsheirer/source/tuner/TunerClass.java
index 90f2e638..ee0074b9 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/TunerClass.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/TunerClass.java
@@ -30,6 +30,7 @@ public enum TunerClass
FUNCUBE_DONGLE_PRO("Funcube Dongle Pro" ),
FUNCUBE_DONGLE_PRO_PLUS("Funcube Dongle Pro+" ),
HACKRF("HackRF" ),
+ HYDRASDR("HydraSDR"),
RTL2832("RTL-2832"),
RSP("RSP"),
TEST_TUNER("Test"),
@@ -53,8 +54,8 @@ public enum TunerClass
return mDescription;
}
- public static final EnumSet SUPPORTED_USB_TUNERS = EnumSet.of(AIRSPY, AIRSPY_HF, HACKRF, RTL2832,
- FUNCUBE_DONGLE_PRO, FUNCUBE_DONGLE_PRO_PLUS);
+ public static final EnumSet SUPPORTED_USB_TUNERS = EnumSet.of(AIRSPY, AIRSPY_HF, HACKRF, HYDRASDR,
+ RTL2832, FUNCUBE_DONGLE_PRO, FUNCUBE_DONGLE_PRO_PLUS);
public static final EnumSet FUNCUBE_TUNERS = EnumSet.of(FUNCUBE_DONGLE_PRO, FUNCUBE_DONGLE_PRO_PLUS);
@@ -121,6 +122,8 @@ public enum TunerClass
case 0x1D50CC15: //HACKRF ONE
case 0x1D50604B: //HACKRF JAWBREAKER
return HACKRF;
+ case 0x38AF0001: //HYDRASDR RFONE (official VID 38af, PID 0001)
+ return HYDRASDR;
case 0x1D5060A1:
return AIRSPY;
case 0x03EB800C:
diff --git a/src/main/java/io/github/dsheirer/source/tuner/TunerFactory.java b/src/main/java/io/github/dsheirer/source/tuner/TunerFactory.java
index aac25d6d..12639076 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/TunerFactory.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/TunerFactory.java
@@ -44,6 +44,10 @@ import io.github.dsheirer.source.tuner.hackrf.HackRFTuner;
import io.github.dsheirer.source.tuner.hackrf.HackRFTunerConfiguration;
import io.github.dsheirer.source.tuner.hackrf.HackRFTunerController;
import io.github.dsheirer.source.tuner.hackrf.HackRFTunerEditor;
+import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTuner;
+import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTunerConfiguration;
+import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTunerController;
+import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTunerEditor;
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
import io.github.dsheirer.source.tuner.manager.TunerManager;
import io.github.dsheirer.source.tuner.recording.RecordingTunerConfiguration;
@@ -375,6 +379,8 @@ public class TunerFactory
throw new SourceException("Unable to find matching tuner sound card mixer");
case HACKRF:
return new HackRFTuner(new HackRFTunerController(bus, portAddress, tunerErrorListener), tunerErrorListener, channelizerType);
+ case HYDRASDR:
+ return new HydraSdrTuner(new HydraSdrTunerController(bus, portAddress, tunerErrorListener), tunerErrorListener, channelizerType);
case RTL2832:
return new RTL2832Tuner(new RTL2832TunerController(bus, portAddress, tunerErrorListener), tunerErrorListener, channelizerType);
default:
@@ -424,6 +430,8 @@ public class TunerFactory
case HACKRF_ONE:
case HACKRF_RAD1O:
return new HackRFTunerConfiguration(uniqueID);
+ case HYDRASDR_R828D:
+ return new HydraSdrTunerConfiguration(uniqueID);
case RAFAELMICRO_R820T:
return new R820TTunerConfiguration(uniqueID);
case RAFAELMICRO_R828D:
@@ -467,6 +475,8 @@ public class TunerFactory
return new FCD2TunerEditor(userPreferences, tunerManager, discoveredTuner);
case HACKRF:
return new HackRFTunerEditor(userPreferences, tunerManager, discoveredTuner);
+ case HYDRASDR:
+ return new HydraSdrTunerEditor(userPreferences, tunerManager, discoveredTuner);
case RSP:
if(discoveredTuner instanceof DiscoveredRspTuner discoveredRspTuner)
{
diff --git a/src/main/java/io/github/dsheirer/source/tuner/TunerType.java b/src/main/java/io/github/dsheirer/source/tuner/TunerType.java
index 59f2df7b..f2e10806 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/TunerType.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/TunerType.java
@@ -36,6 +36,7 @@ public enum TunerType
HACKRF_ONE("ONE"),
HACKRF_JAWBREAKER("Jawbreaker"),
HACKRF_RAD1O("RAD1O"),
+ HYDRASDR_R828D("R828D"),
RAFAELMICRO_R820T("R820T"),
RAFAELMICRO_R828D("R828D"),
RSP_1("RSP1"),
@@ -73,7 +74,7 @@ public enum TunerType
* Supported USB tuner types
*/
public static EnumSet SUPPORTED_USB_TUNERS = EnumSet.of(AIRSPY_R820T, ELONICS_E4000, FITIPOWER_FC0012,
- HACKRF_JAWBREAKER, HACKRF_RAD1O, HACKRF_ONE, RAFAELMICRO_R820T);
+ HACKRF_JAWBREAKER, HACKRF_RAD1O, HACKRF_ONE, HYDRASDR_R828D, RAFAELMICRO_R820T);
/**
* Supported sound card tuner types
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 9e7774f2..b9abf805 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-2024 Dennis Sheirer
+ * 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
@@ -20,7 +20,7 @@
package io.github.dsheirer.source.tuner.airspy;
import io.github.dsheirer.buffer.INativeBufferFactory;
-import io.github.dsheirer.buffer.airspy.AirspyNativeBufferFactory;
+import io.github.dsheirer.buffer.sample.SampleNativeBufferFactory;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.ITunerErrorListener;
import io.github.dsheirer.source.tuner.TunerType;
@@ -69,7 +69,7 @@ public class AirspyTunerController extends USBTunerController
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 AirspyNativeBufferFactory mNativeBufferFactory = new AirspyNativeBufferFactory();
+ private SampleNativeBufferFactory mNativeBufferFactory = new SampleNativeBufferFactory();
private AirspyDeviceInformation mDeviceInfo;
private List mSampleRates = new ArrayList<>();
private int mSampleRate = 0;
diff --git a/src/main/java/io/github/dsheirer/source/tuner/airspy/hf/AirspyHfTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/airspy/hf/AirspyHfTunerController.java
index 5b380d37..5521a80b 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/airspy/hf/AirspyHfTunerController.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/airspy/hf/AirspyHfTunerController.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2024 Dennis Sheirer
+ * 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
@@ -20,7 +20,7 @@
package io.github.dsheirer.source.tuner.airspy.hf;
import io.github.dsheirer.buffer.INativeBufferFactory;
-import io.github.dsheirer.buffer.airspy.hf.AirspyHfNativeBufferFactory;
+import io.github.dsheirer.buffer.airspy.AirspyHfNativeBufferFactory;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.ITunerErrorListener;
import io.github.dsheirer.source.tuner.TunerType;
diff --git a/src/main/java/io/github/dsheirer/source/tuner/configuration/TunerConfiguration.java b/src/main/java/io/github/dsheirer/source/tuner/configuration/TunerConfiguration.java
index 160c5cf6..e537d510 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/configuration/TunerConfiguration.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/configuration/TunerConfiguration.java
@@ -29,6 +29,7 @@ import io.github.dsheirer.source.tuner.airspy.hf.AirspyHfTunerConfiguration;
import io.github.dsheirer.source.tuner.fcd.proV1.FCD1TunerConfiguration;
import io.github.dsheirer.source.tuner.fcd.proplusV2.FCD2TunerConfiguration;
import io.github.dsheirer.source.tuner.hackrf.HackRFTunerConfiguration;
+import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTunerConfiguration;
import io.github.dsheirer.source.tuner.recording.RecordingTunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.fc0013.FC0013TunerConfiguration;
@@ -48,6 +49,7 @@ import io.github.dsheirer.source.tuner.sdrplay.RspTunerConfiguration;
@JsonSubTypes.Type(value = FCD1TunerConfiguration.class, name = "fcd1TunerConfiguration"),
@JsonSubTypes.Type(value = FCD2TunerConfiguration.class, name = "fcd2TunerConfiguration"),
@JsonSubTypes.Type(value = HackRFTunerConfiguration.class, name = "hackRFTunerConfiguration"),
+ @JsonSubTypes.Type(value = HydraSdrTunerConfiguration.class, name = "hydraSdrTunerConfiguration"),
@JsonSubTypes.Type(value = RecordingTunerConfiguration.class, name = "recordingTunerConfiguration"),
@JsonSubTypes.Type(value = R820TTunerConfiguration.class, name = "r820TTunerConfiguration"),
@JsonSubTypes.Type(value = R828DTunerConfiguration.class, name = "r828DTunerConfiguration"),
diff --git a/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrDeviceInformation.java b/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrDeviceInformation.java
new file mode 100644
index 00000000..21fe36fe
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrDeviceInformation.java
@@ -0,0 +1,157 @@
+package io.github.dsheirer.source.tuner.hydrasdr;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+
+public class HydraSdrDeviceInformation
+{
+ private final static Logger mLog =
+ LoggerFactory.getLogger( HydraSdrDeviceInformation.class );
+
+ private HydraSdrTunerController.BoardID mBoardID = HydraSdrTunerController.BoardID.UNKNOWN;
+ private String mFirmwareVersion = "Unknown";
+ private String mPartNumber = "Unknown";
+ private String mSerialNumber = "Unknown";
+
+ /**
+ * HydraSDR Device Information - provides parsing and access to device
+ * information components.
+ */
+ public HydraSdrDeviceInformation()
+ {
+ }
+
+ public void setBoardID( int boardID )
+ {
+ mBoardID = HydraSdrTunerController.BoardID.fromValue( boardID );
+ }
+
+ public HydraSdrTunerController.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( "HydraSDR ", "" );
+ return;
+ }
+ }
+
+ mFirmwareVersion = new String( data );
+ mFirmwareVersion = mFirmwareVersion.replace( "HydraSDR ", "" );
+ }
+ else
+ {
+ mLog.error( "Error setting HydraSDR version byte data - null:" +
+ ( data == null ? " TRUE" : " FALSE length:" + data.length ) );
+ }
+ }
+
+ public String getVersion()
+ {
+ return mFirmwareVersion;
+ }
+
+ public void setPartAndSerialNumber( byte[] data )
+ {
+ if( data != null && data.length == 24 )
+ {
+ //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 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();
+ }
+ else
+ {
+ mLog.error( "Error setting HydraSDR part and serial byte data - null:" +
+ ( data == null ? " TRUE" : " FALSE length:" + data.length ) );
+ }
+ }
+
+ private String format( byte value )
+ {
+ return String.format( "%02X", value );
+ }
+
+ public String getSerialNumber()
+ {
+ return mSerialNumber;
+ }
+
+ public String getPartNumber()
+ {
+ return mPartNumber;
+ }
+
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append( "HydraSDR 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() );
+
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrSampleRate.java b/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrSampleRate.java
new file mode 100644
index 00000000..ca392e3f
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrSampleRate.java
@@ -0,0 +1,35 @@
+package io.github.dsheirer.source.tuner.hydrasdr;
+
+public class HydraSdrSampleRate
+{
+ private int mIndex;
+ private int mValue;
+ private String mLabel;
+
+ public HydraSdrSampleRate( int index, int value, String label )
+ {
+ mIndex = index;
+ mValue = value;
+ mLabel = label;
+ }
+
+ public int getIndex()
+ {
+ return mIndex;
+ }
+
+ public int getRate()
+ {
+ return mValue;
+ }
+
+ public String getLabel()
+ {
+ return mLabel;
+ }
+
+ public String toString()
+ {
+ return getLabel();
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrTuner.java b/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrTuner.java
new file mode 100644
index 00000000..134dbd6b
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrTuner.java
@@ -0,0 +1,95 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+package io.github.dsheirer.source.tuner.hydrasdr;
+
+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.hydrasdr.HydraSdrTunerController.BoardID;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * HydraSDR Tuner
+ */
+public class HydraSdrTuner extends Tuner
+{
+ private final static Logger mLog = LoggerFactory.getLogger(HydraSdrTuner.class);
+
+ /**
+ * Constructs an instance
+ * @param controller for the HydraSDR
+ * @param tunerErrorListener to listen for errors from this tuner
+ * @param channelizerType for the channelizer
+ */
+ public HydraSdrTuner(HydraSdrTunerController controller, ITunerErrorListener tunerErrorListener,
+ ChannelizerType channelizerType)
+ {
+ super(controller, tunerErrorListener, channelizerType);
+ }
+
+ @Override
+ public String getPreferredName()
+ {
+ return "HydraSDR " + getController().getDeviceInfo().getSerialNumber();
+ }
+
+ /**
+ * HydraSDR tuner controller
+ */
+ public HydraSdrTunerController getController()
+ {
+ return (HydraSdrTunerController)getTunerController();
+ }
+
+ @Override
+ public String getUniqueID()
+ {
+ try
+ {
+ return getController().getDeviceInfo().getSerialNumber();
+ }
+ catch(Exception e)
+ {
+ mLog.error("error getting serial number", e);
+ }
+
+ return BoardID.HYDRASDR_RFONE.getLabel();
+ }
+
+ @Override
+ public TunerClass getTunerClass()
+ {
+ return TunerClass.HYDRASDR;
+ }
+
+ @Override
+ public double getSampleSize()
+ {
+ return 13.0;
+ }
+
+ @Override
+ public int getMaximumUSBBitsPerSecond()
+ {
+ //4-bytes per sample = 32 bits times 10 MSps = 320,000,000 bits per second
+ return 320000000;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrTunerConfiguration.java b/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrTunerConfiguration.java
new file mode 100644
index 00000000..b5d71bf3
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrTunerConfiguration.java
@@ -0,0 +1,144 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 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.hydrasdr;
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import io.github.dsheirer.source.tuner.TunerType;
+import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTunerController.Gain;
+import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
+
+public class HydraSdrTunerConfiguration extends TunerConfiguration
+{
+ private Gain mGain = HydraSdrTunerController.LINEARITY_GAIN_DEFAULT;
+ private int mSampleRate = HydraSdrTunerController.DEFAULT_SAMPLE_RATE.getRate();
+ private int mIFGain = HydraSdrTunerController.IF_GAIN_DEFAULT;
+ private int mMixerGain = HydraSdrTunerController.MIXER_GAIN_DEFAULT;
+ private int mLNAGain = HydraSdrTunerController.LNA_GAIN_DEFAULT;
+ private boolean mMixerAGC = false;
+ private boolean mLNAAGC = false;
+ private boolean mBiasT = false;
+
+ /**
+ * Default constructor for JAXB
+ */
+ public HydraSdrTunerConfiguration()
+ {
+ super(HydraSdrTunerController.MINIMUM_TUNABLE_FREQUENCY_HZ, HydraSdrTunerController.MAXIMUM_TUNABLE_FREQUENCY_HZ);
+ }
+
+ @Override
+ @JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
+ public TunerType getTunerType()
+ {
+ return TunerType.HYDRASDR_R828D;
+ }
+
+ public HydraSdrTunerConfiguration(String uniqueID)
+ {
+ super(uniqueID);
+ }
+
+ @JacksonXmlProperty(isAttribute = true, localName = "sample_rate")
+ public int getSampleRate()
+ {
+ return mSampleRate;
+ }
+
+ public void setSampleRate(int sampleRate)
+ {
+ mSampleRate = sampleRate;
+ }
+
+ @JacksonXmlProperty(isAttribute = true, localName = "gain")
+ public Gain getGain()
+ {
+ return mGain;
+ }
+
+ public void setGain(Gain gain)
+ {
+ mGain = gain;
+ }
+
+ @JacksonXmlProperty(isAttribute = true, localName = "if_gain")
+ public int getIFGain()
+ {
+ return mIFGain;
+ }
+
+ public void setIFGain(int gain)
+ {
+ mIFGain = gain;
+ }
+
+ @JacksonXmlProperty(isAttribute = true, localName = "mixer_gain")
+ public int getMixerGain()
+ {
+ return mMixerGain;
+ }
+
+ public void setMixerGain(int gain)
+ {
+ mMixerGain = gain;
+ }
+
+ @JacksonXmlProperty(isAttribute = true, localName = "lna_gain")
+ public int getLNAGain()
+ {
+ return mLNAGain;
+ }
+
+ public void setLNAGain(int gain)
+ {
+ mLNAGain = gain;
+ }
+
+ @JacksonXmlProperty(isAttribute = true, localName = "mixer_agc")
+ public boolean isMixerAGC()
+ {
+ return mMixerAGC;
+ }
+
+ public void setMixerAGC(boolean enabled)
+ {
+ mMixerAGC = enabled;
+ }
+
+ @JacksonXmlProperty(isAttribute = true, localName = "lna_agc")
+ public boolean isLNAAGC()
+ {
+ return mLNAAGC;
+ }
+
+ public void setLNAAGC(boolean enabled)
+ {
+ mLNAAGC = enabled;
+ }
+
+ @JacksonXmlProperty(isAttribute = true, localName = "bias_t")
+ public boolean isBiasT()
+ {
+ return mBiasT;
+ }
+
+ public void setBiasT(boolean enabled)
+ {
+ mBiasT = enabled;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrTunerController.java
new file mode 100644
index 00000000..3b72b25d
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrTunerController.java
@@ -0,0 +1,1054 @@
+/*
+ * *****************************************************************************
+ * 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.source.tuner.hydrasdr;
+
+import io.github.dsheirer.buffer.INativeBufferFactory;
+import io.github.dsheirer.buffer.sample.SampleNativeBufferFactory;
+import io.github.dsheirer.source.SourceException;
+import io.github.dsheirer.source.tuner.ITunerErrorListener;
+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 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.LibUsb;
+import org.usb4java.LibUsbException;
+
+import javax.usb.UsbException;
+
+/**
+ * HydraSDR tuner controller
+ */
+public class HydraSdrTunerController extends USBTunerController
+{
+ private final static Logger mLog = LoggerFactory.getLogger(HydraSdrTunerController.class);
+ public static final Gain LINEARITY_GAIN_DEFAULT = Gain.LINEARITY_14;
+ public static final Gain SENSITIVITY_GAIN_DEFAULT = Gain.SENSITIVITY_10;
+ public static final int GAIN_MIN = 1;
+ public static final int GAIN_MAX = 22;
+ public static final int LNA_GAIN_MIN = 0;
+ public static final int LNA_GAIN_MAX = 14;
+ public static final int LNA_GAIN_DEFAULT = 7;
+ public static final int MIXER_GAIN_MIN = 0;
+ public static final int MIXER_GAIN_MAX = 15;
+ public static final int MIXER_GAIN_DEFAULT = 9;
+ public static final int IF_GAIN_MIN = 0;
+ public static final int IF_GAIN_MAX = 15;
+ public static final int IF_GAIN_DEFAULT = 9;
+ public static final long MINIMUM_TUNABLE_FREQUENCY_HZ = 24000000l;
+ 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 HydraSdrSampleRate DEFAULT_SAMPLE_RATE = new HydraSdrSampleRate(0, 10000000, "10.00 MHz");
+ private SampleNativeBufferFactory mNativeBufferFactory = new SampleNativeBufferFactory();
+ private HydraSdrDeviceInformation mDeviceInfo;
+ private List mSampleRates = new ArrayList<>();
+ private int mSampleRate = 0;
+
+ /**
+ * Constructs an instance
+ * @param bus usb
+ * @param portAddress usb
+ * @param tunerErrorListener to receive error notifications from this controller
+ */
+ public HydraSdrTunerController(int bus, String portAddress, ITunerErrorListener tunerErrorListener)
+ {
+ super(bus, portAddress, MINIMUM_TUNABLE_FREQUENCY_HZ, MAXIMUM_TUNABLE_FREQUENCY_HZ, 0, USABLE_BANDWIDTH_PERCENT, tunerErrorListener);
+ }
+
+ @Override
+ public TunerType getTunerType()
+ {
+ return TunerType.HYDRASDR_R828D;
+ }
+
+ @Override
+ protected INativeBufferFactory getNativeBufferFactory()
+ {
+ return mNativeBufferFactory;
+ }
+
+ @Override
+ protected int getTransferBufferSize()
+ {
+ return USB_TRANSFER_BUFFER_SIZE;
+ }
+
+ @Override
+ public int getBufferSampleCount()
+ {
+ //Each HydraSDR complex sample is 4 bytes, so sample count is buffer size / 4
+ return USB_TRANSFER_BUFFER_SIZE / 4;
+ }
+
+ @Override
+ protected void deviceStart() throws SourceException
+ {
+ try
+ {
+ setSamplePacking(false);
+ }
+ catch(LibUsbException | UsbException | UnsupportedOperationException e)
+ {
+ mLog.info("Sample packing is not supported by HydraSDR firmware");
+ }
+
+ try
+ {
+ setReceiverMode(true);
+ }
+ catch(Exception e)
+ {
+ mLog.error("Couldn't enable HydraSDR receiver mode", e);
+ }
+
+ setFrequency(FREQUENCY_DEFAULT);
+
+ try
+ {
+ determineAvailableSampleRates();
+ }
+ catch(LibUsbException | UsbException e)
+ {
+ mLog.error("Error identifying available samples rates", e);
+ }
+
+ try
+ {
+ setSampleRate(mSampleRates.get(0));
+ }
+ catch(IllegalArgumentException | LibUsbException | UsbException e)
+ {
+ mLog.error("Setting sample rate is not supported by firmware", e);
+ }
+ }
+
+ @Override
+ protected void deviceStop()
+ {
+ try
+ {
+ setReceiverMode(false);
+ }
+ catch(Exception e)
+ {
+ mLog.error("Couldn't enable HydraSDR receiver mode", e);
+ }
+ }
+
+ @Override
+ public void apply(TunerConfiguration tunerConfiguration) throws SourceException
+ {
+ //Invoke super for frequency, frequency correction and autoPPM
+ super.apply(tunerConfiguration);
+
+ if(tunerConfiguration instanceof HydraSdrTunerConfiguration config)
+ {
+ int sampleRate = config.getSampleRate();
+ HydraSdrSampleRate 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);
+ }
+
+ try
+ {
+ setFrequency(config.getFrequency());
+ setFrequencyCorrection(config.getFrequencyCorrection());
+
+ setIFGain(config.getIFGain());
+ setMixerGain(config.getMixerGain());
+ setLNAGain(config.getLNAGain());
+ setMixerAGC(config.isMixerAGC());
+ setLNAAGC(config.isLNAAGC());
+ setBiasT(config.isBiasT());
+
+ //Set the gain mode last, so custom values are already set, and linearity and sensitivity modes will
+ //automatically override the custom values.
+ setGain(config.getGain());
+ }
+ catch(Exception e)
+ {
+ throw new SourceException("Couldn't apply settings from HydraSDR config", e);
+ }
+ }
+ else
+ {
+ throw new IllegalArgumentException("Invalid tuner config:" + tunerConfiguration.getClass());
+ }
+ }
+
+ @Override
+ public long getTunedFrequency() throws SourceException
+ {
+ return mFrequencyController.getTunedFrequency();
+ }
+
+ @Override
+ public synchronized 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);
+ }
+ }
+ else
+ {
+ throw new SourceException("Frequency [" + frequency + "] outside " + "of tunable range " + MINIMUM_TUNABLE_FREQUENCY_HZ + "-" + MAXIMUM_TUNABLE_FREQUENCY_HZ);
+ }
+ }
+
+ @Override
+ public double getCurrentSampleRate()
+ {
+ return mSampleRate;
+ }
+
+ /**
+ * Sets the sample rate to the rate specified by the index value in the available sample rates map
+ *
+ * @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
+ */
+ public void setSampleRate(HydraSdrSampleRate rate) throws LibUsbException, UsbException, SourceException
+ {
+ getNativeBufferFactory().setSamplesPerMillisecond((float)rate.getRate() / 1000.0f);
+ if(rate.getRate() != mSampleRate)
+ {
+ int result = readByte(Command.SET_SAMPLE_RATE, 0, rate.getIndex(), true);
+
+ if(result != 1)
+ {
+ throw new UsbException("Error setting sample rate [" + rate + "] rate - return value [" + result + "]");
+ }
+ else
+ {
+ mSampleRate = rate.getRate();
+ mFrequencyController.setSampleRate(mSampleRate);
+
+ // Update the Usable Bandwidth Percentage, as the usable range varies with the sample rate.
+ 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;
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns a list of sample rates supported by the firmware version
+ */
+ public List getSampleRates()
+ {
+ return mSampleRates;
+ }
+
+ /**
+ * HydraSDR sample rate object that matches the current sample rate setting.
+ */
+ public HydraSdrSampleRate getHydraSdrSampleRate()
+ {
+ return getSampleRate(mSampleRate);
+ }
+
+ /**
+ * HydraSDR sample rate object that matches the specified rate in hertz, or
+ * null if there are no available sample rates for the tuner that match the
+ * argument value.
+ */
+ public HydraSdrSampleRate getSampleRate(int rate)
+ {
+ for(HydraSdrSampleRate sampleRate : mSampleRates)
+ {
+ if(sampleRate.getRate() == rate)
+ {
+ return sampleRate;
+ }
+ }
+
+ //We should never get to here ...
+ 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
+ */
+ public synchronized void setMixerAGC(boolean enabled) throws LibUsbException, UsbException
+ {
+ 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);
+ }
+ }
+
+ /**
+ * Enables/disables the low noise amplifier automatic gain setting
+ *
+ * @param enabled
+ * @throws LibUsbException on unsuccessful read operation
+ * @throws UsbException on USB error
+ */
+ public synchronized void setLNAAGC(boolean enabled) throws LibUsbException, UsbException
+ {
+ 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);
+ }
+ }
+
+ public void setGain(Gain gain) throws UsbException
+ {
+ if(gain != Gain.CUSTOM)
+ {
+ setMixerAGC(false);
+ setLNAAGC(false);
+ setLNAGain(gain.getLNA());
+ setMixerGain(gain.getMixer());
+ setIFGain(gain.getIF());
+ }
+ }
+
+ /**
+ * 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 IllegalArgumentException if gain value is invalid
+ */
+ public synchronized void setLNAGain(int gain) throws LibUsbException, UsbException, 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);
+ }
+ }
+ else
+ {
+ throw new IllegalArgumentException("LNA gain value [" + gain + "] is outside value range: " +
+ LNA_GAIN_MIN + "-" + LNA_GAIN_MAX);
+ }
+ }
+
+ /**
+ * 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 IllegalArgumentException if gain value is invalid
+ */
+ public void setMixerGain(int gain) throws LibUsbException, UsbException, 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);
+ }
+ }
+ else
+ {
+ throw new IllegalArgumentException("Mixer gain value [" + gain +
+ "] is outside value range: " + MIXER_GAIN_MIN + "-" + MIXER_GAIN_MAX);
+ }
+ }
+
+ /**
+ * 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
+ * @throws IllegalArgumentException if gain value is invalid
+ */
+ public void setIFGain(int gain) throws LibUsbException, UsbException, 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);
+ }
+ }
+ else
+ {
+ throw new IllegalArgumentException("VGA gain value [" + gain +
+ "] is outside value range: " + IF_GAIN_MIN + "-" + IF_GAIN_MAX);
+ }
+ }
+
+ 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);
+ }
+
+ /**
+ * Enables/disables the Bias-T (RF bias tee) power output for active antennas
+ *
+ * @param enabled true to enable Bias-T, false to disable
+ * @throws LibUsbException on unsuccessful write operation
+ * @throws UsbException on USB error
+ */
+ public synchronized void setBiasT(boolean enabled) throws LibUsbException, UsbException
+ {
+ ByteBuffer buffer = ByteBuffer.allocateDirect(0);
+ write(Command.SET_RF_BIAS_COMMAND, 0, (enabled ? 1 : 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 HydraSdrSampleRate(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
+ */
+ private static String formatSampleRate(int rate)
+ {
+ return MHZ_FORMATTER.format((double) rate / 1E6d);
+ }
+
+ /**
+ * Device information
+ */
+ public HydraSdrDeviceInformation 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 HydraSdrDeviceInformation();
+ }
+
+ /* Board ID */
+ try
+ {
+ int boardID = readByte(Command.BOARD_ID_READ, 0, 0, true);
+ mDeviceInfo.setBoardID(boardID);
+ }
+ catch(LibUsbException | UsbException e)
+ {
+ mLog.error("Error reading HydraSDR board ID", e);
+ }
+
+ /* Version String */
+ try
+ {
+ byte[] version = readArray(Command.VERSION_STRING_READ, 0, 0, 127);
+ mDeviceInfo.setVersion(version);
+ }
+ catch(LibUsbException | UsbException e)
+ {
+ mLog.error("Error reading HydraSDR 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 HydraSDR version string", e);
+ }
+ }
+
+ /**
+ * Reads a single byte value from the device.
+ *
+ * @param command - HydraSDR 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 - HydraSDR 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 - HydraSDR 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,
+ SENSITIVITY,
+ CUSTOM;
+ }
+
+ public enum Gain
+ {
+ LINEARITY_1(1, 4, 0, 0),
+ LINEARITY_2(2, 5, 0, 0),
+ LINEARITY_3(3, 6, 1, 0),
+ LINEARITY_4(4, 7, 1, 0),
+ LINEARITY_5(5, 8, 1, 0),
+ LINEARITY_6(6, 9, 1, 0),
+ LINEARITY_7(7, 10, 2, 0),
+ LINEARITY_8(8, 10, 2, 1),
+ LINEARITY_9(9, 10, 0, 3),
+ LINEARITY_10(10, 10, 0, 5),
+ LINEARITY_11(11, 10, 1, 6),
+ LINEARITY_12(12, 10, 0, 8),
+ LINEARITY_13(13, 10, 0, 9),
+ LINEARITY_14(14, 10, 5, 8),
+ LINEARITY_15(15, 10, 6, 9),
+ LINEARITY_16(16, 11, 6, 9),
+ LINEARITY_17(17, 11, 7, 10),
+ LINEARITY_18(18, 11, 8, 12),
+ LINEARITY_19(19, 11, 9, 13),
+ LINEARITY_20(20, 11, 11, 14),
+ LINEARITY_21(21, 12, 12, 14),
+ LINEARITY_22(22, 13, 12, 14),
+ SENSITIVITY_1(1, 4, 0, 0),
+ SENSITIVITY_2(2, 4, 0, 1),
+ SENSITIVITY_3(3, 4, 0, 2),
+ SENSITIVITY_4(4, 4, 0, 3),
+ SENSITIVITY_5(5, 4, 1, 5),
+ SENSITIVITY_6(6, 4, 2, 6),
+ SENSITIVITY_7(7, 4, 2, 7),
+ SENSITIVITY_8(8, 4, 3, 8),
+ SENSITIVITY_9(9, 4, 4, 9),
+ SENSITIVITY_10(10, 5, 4, 9),
+ SENSITIVITY_11(11, 5, 4, 12),
+ SENSITIVITY_12(12, 5, 7, 12),
+ SENSITIVITY_13(13, 5, 8, 13),
+ SENSITIVITY_14(14, 5, 9, 14),
+ SENSITIVITY_15(15, 6, 9, 14),
+ SENSITIVITY_16(16, 7, 10, 14),
+ SENSITIVITY_17(17, 8, 10, 14),
+ SENSITIVITY_18(18, 9, 11, 14),
+ SENSITIVITY_19(19, 10, 12, 14),
+ SENSITIVITY_20(20, 11, 12, 14),
+ SENSITIVITY_21(21, 12, 12, 14),
+ SENSITIVITY_22(22, 13, 12, 14),
+ CUSTOM(1, 0, 0, 0);
+
+ private int mValue;
+ private int mIF;
+ private int mMixer;
+ private int mLNA;
+
+ private Gain(int value, int ifGain, int mixer, int lna)
+ {
+ mValue = value;
+ mIF = ifGain;
+ mMixer = mixer;
+ mLNA = lna;
+ }
+
+ public int getValue()
+ {
+ return mValue;
+ }
+
+ public int getIF()
+ {
+ return mIF;
+ }
+
+ public int getMixer()
+ {
+ return mMixer;
+ }
+
+ public int getLNA()
+ {
+ return mLNA;
+ }
+
+ public static Gain getGain(GainMode mode, int value)
+ {
+ assert (GAIN_MIN <= value && value <= GAIN_MAX);
+
+ switch(mode)
+ {
+ case LINEARITY:
+ for(Gain gain : getLinearityGains())
+ {
+ if(gain.getValue() == value)
+ {
+ return gain;
+ }
+ }
+ return LINEARITY_GAIN_DEFAULT;
+ case SENSITIVITY:
+ for(Gain gain : getSensitivityGains())
+ {
+ if(gain.getValue() == value)
+ {
+ return gain;
+ }
+ }
+ return SENSITIVITY_GAIN_DEFAULT;
+ case CUSTOM:
+ default:
+ return Gain.CUSTOM;
+ }
+ }
+
+ public GainMode getGainMode()
+ {
+ if(getLinearityGains().contains(this))
+ {
+ return GainMode.LINEARITY;
+ }
+ else if(getSensitivityGains().contains(this))
+ {
+ return GainMode.SENSITIVITY;
+ }
+
+ return GainMode.CUSTOM;
+ }
+
+ public static EnumSet getLinearityGains()
+ {
+ return EnumSet.range(LINEARITY_1, LINEARITY_22);
+ }
+
+ public static EnumSet getSensitivityGains()
+ {
+ return EnumSet.range(SENSITIVITY_1, SENSITIVITY_22);
+ }
+ }
+
+ /**
+ * HydraSDR Board Identifier
+ */
+ public enum BoardID
+ {
+ HYDRASDR_PROTO(0, "HydraSDR Proto"),
+ HYDRASDR_RFONE(1, "HydraSDR RF-One"),
+ 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 HYDRASDR_PROTO;
+ }
+ else if(value == 1)
+ {
+ return HYDRASDR_RFONE;
+ }
+
+ return UNKNOWN;
+ }
+ }
+
+ /**
+ * HydraSDR Commands
+ */
+ public enum Command
+ {
+ INVALID(0),
+ RECEIVER_MODE(1),
+ SI5351C_WRITE(2),
+ SI5351C_READ(3),
+ R828D_WRITE(4),
+ R828D_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 HydraSDR 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 HydraSDR 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;
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrTunerEditor.java b/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrTunerEditor.java
new file mode 100644
index 00000000..cbde8c5c
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrTunerEditor.java
@@ -0,0 +1,742 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 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.hydrasdr;
+
+import io.github.dsheirer.preference.UserPreferences;
+import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTunerController.Gain;
+import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTunerController.GainMode;
+import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
+import io.github.dsheirer.source.tuner.manager.TunerManager;
+import io.github.dsheirer.source.tuner.manager.TunerStatus;
+import io.github.dsheirer.source.tuner.ui.TunerEditor;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.List;
+import net.miginfocom.swing.MigLayout;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JSeparator;
+import javax.swing.JSlider;
+import javax.swing.SpinnerNumberModel;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+/**
+ * HydraSDR tuner editor/controller
+ */
+public class HydraSdrTunerEditor extends TunerEditor
+{
+ private static final long serialVersionUID = 1L;
+ private final static Logger mLog = LoggerFactory.getLogger(HydraSdrTunerEditor.class);
+ private JButton mTunerInfoButton;
+ private JComboBox mSampleRateCombo;
+ private JComboBox mGainModeCombo;
+ private JSlider mMasterGainSlider;
+ private JLabel mMasterGainLabel;
+ private JLabel mMasterGainValueLabel;
+ private JSlider mIFGainSlider;
+ private JLabel mIFGainLabel;
+ private JLabel mIFGainValueLabel;
+ private JSlider mLNAGainSlider;
+ private JLabel mLNAGainValueLabel;
+ private JSlider mMixerGainSlider;
+ private JLabel mMixerGainValueLabel;
+ private JCheckBox mLNAAGCCheckBox;
+ private JCheckBox mMixerAGCCheckBox;
+ private JCheckBox mBiasTCheckBox;
+
+ /**
+ * Constructs an instance
+ * @param userPreferences for wide-band recordings
+ * @param tunerManager for saving tuner configuration
+ * @param discoveredTuner for the optionally usable HydraSDR tuner and controller
+ */
+ public HydraSdrTunerEditor(UserPreferences userPreferences, TunerManager tunerManager, DiscoveredTuner discoveredTuner)
+ {
+ super(userPreferences, tunerManager, discoveredTuner);
+ init();
+ tunerStatusUpdated();
+ }
+
+ @Override
+ public long getMinimumTunableFrequency()
+ {
+ return HydraSdrTunerController.MINIMUM_TUNABLE_FREQUENCY_HZ;
+ }
+
+ @Override
+ public long getMaximumTunableFrequency()
+ {
+ return HydraSdrTunerController.MAXIMUM_TUNABLE_FREQUENCY_HZ;
+ }
+
+ @Override
+ protected void tunerStatusUpdated()
+ {
+ setLoading(true);
+
+ if(hasTuner())
+ {
+ getTunerIdLabel().setText(getTuner().getPreferredName());
+ }
+ else
+ {
+ getTunerIdLabel().setText(getDiscoveredTuner().getId());
+ }
+
+ String status = getDiscoveredTuner().getTunerStatus().toString();
+ if(getDiscoveredTuner().hasErrorMessage())
+ {
+ status += " - " + getDiscoveredTuner().getErrorMessage();
+ }
+ getTunerStatusLabel().setText(status);
+ getButtonPanel().updateControls();
+ getFrequencyPanel().updateControls();
+ getSampleRateCombo().setEnabled(hasTuner() && !getTuner().getTunerController().isLockedSampleRate());
+ getTunerInfoButton().setEnabled(hasTuner());
+ getBiasTCheckBox().setEnabled(hasTuner());
+ if(hasTuner() && hasConfiguration())
+ {
+ getBiasTCheckBox().setSelected(getConfiguration().isBiasT());
+ }
+ updateGainComponents((hasTuner() && hasConfiguration()) ? getConfiguration().getGain() : null);
+
+ if(hasTuner())
+ {
+ List rates = getTuner().getController().getSampleRates();
+ getSampleRateCombo().setModel(new DefaultComboBoxModel<>(rates.toArray(new HydraSdrSampleRate[rates.size()])));
+
+ if(hasConfiguration())
+ {
+ HydraSdrSampleRate sampleRate = getSampleRate(getConfiguration().getSampleRate());
+ getSampleRateCombo().setSelectedItem(sampleRate);
+ }
+ }
+ else
+ {
+ getSampleRateCombo().setModel(new DefaultComboBoxModel<>());
+ }
+
+ setLoading(false);
+ }
+
+ private void init()
+ {
+ setLayout(new MigLayout("fill,wrap 3", "[right][grow,fill][fill]",
+ "[][][][][][][][][][][][][][][][grow]"));
+
+ add(new JLabel("Tuner:"));
+ add(getTunerIdLabel());
+ add(getTunerInfoButton());
+
+ add(new JLabel("Status:"));
+ add(getTunerStatusLabel(), "wrap");
+
+ add(getButtonPanel(), "span,align left");
+
+ add(new JSeparator(), "span,growx,push");
+
+ add(new JLabel("Frequency (MHz):"));
+ add(getFrequencyPanel(), "wrap");
+
+ add(new JLabel("Sample Rate:"));
+ add(getSampleRateCombo(), "wrap");
+
+ add(new JLabel());
+ add(getBiasTCheckBox(), "wrap");
+
+ add(new JSeparator(), "span,growx,push");
+ add(new JLabel("Gain Control"), "wrap");
+
+ add(new JLabel("Mode:"));
+ add(getGainModeCombo(), "wrap");
+
+ add(getMasterGainLabel());
+ add(getMasterGainSlider());
+ add(getMasterGainValueLabel());
+
+ add(getIFGainLabel());
+ add(getIFGainSlider());
+ add(getIFGainValueLabel());
+
+ add(getMixerAGCCheckBox());
+ add(getMixerGainSlider());
+ add(getMixerGainValueLabel());
+
+ add(getLNAAGCCheckBox());
+ add(getLNAGainSlider());
+ add(getLNAGainValueLabel());
+ }
+
+ private JCheckBox getLNAAGCCheckBox()
+ {
+ if(mLNAAGCCheckBox == null)
+ {
+ mLNAAGCCheckBox = new JCheckBox("AGC LNA:");
+ mLNAAGCCheckBox.setEnabled(false);
+ mLNAAGCCheckBox.addActionListener(e ->
+ {
+ if(hasTuner() && !isLoading())
+ {
+ try
+ {
+ getTuner().getController().setLNAAGC(getLNAAGCCheckBox().isSelected());
+ getLNAGainSlider().setEnabled(!getLNAAGCCheckBox().isSelected());
+ save();
+ }
+ catch(Exception e1)
+ {
+ mLog.error("Error setting LNA AGC Enabled");
+ }
+ }
+ });
+ }
+
+ return mLNAAGCCheckBox;
+ }
+
+ private JCheckBox getMixerAGCCheckBox()
+ {
+ if(mMixerAGCCheckBox == null)
+ {
+ mMixerAGCCheckBox = new JCheckBox("AGC Mixer:");
+ mMixerAGCCheckBox.setEnabled(false);
+ mMixerAGCCheckBox.addActionListener(e ->
+ {
+ if(hasTuner() && !isLoading())
+ {
+ try
+ {
+ getTuner().getController().setMixerAGC(getMixerAGCCheckBox().isSelected());
+ getMixerGainSlider().setEnabled(!getMixerAGCCheckBox().isSelected());
+ save();
+ }
+ catch(Exception e1)
+ {
+ mLog.error("Error setting Mixer AGC Enabled");
+ }
+ }
+ });
+ }
+
+ return mMixerAGCCheckBox;
+ }
+
+ /**
+ * Checkbox to enable/disable Bias-T power for active antennas
+ */
+ private JCheckBox getBiasTCheckBox()
+ {
+ if(mBiasTCheckBox == null)
+ {
+ mBiasTCheckBox = new JCheckBox("Bias-T");
+ mBiasTCheckBox.setEnabled(false);
+ mBiasTCheckBox.setToolTipText("Enable Bias-T power output for active antennas");
+ mBiasTCheckBox.addActionListener(e ->
+ {
+ if(hasTuner() && !isLoading())
+ {
+ try
+ {
+ getTuner().getController().setBiasT(mBiasTCheckBox.isSelected());
+ save();
+ }
+ catch(Exception e1)
+ {
+ mLog.error("Error setting Bias-T", e1);
+ JOptionPane.showMessageDialog(mBiasTCheckBox, "Couldn't set Bias-T: " + e1.getMessage());
+ }
+ }
+ });
+ }
+
+ return mBiasTCheckBox;
+ }
+
+ private JLabel getLNAGainValueLabel()
+ {
+ if(mLNAGainValueLabel == null)
+ {
+ mLNAGainValueLabel = new JLabel("0");
+ mLNAGainValueLabel.setEnabled(false);
+ }
+
+ return mLNAGainValueLabel;
+ }
+
+ private JSlider getLNAGainSlider()
+ {
+ if(mLNAGainSlider == null)
+ {
+ mLNAGainSlider = new JSlider(JSlider.HORIZONTAL, HydraSdrTunerController.LNA_GAIN_MIN,
+ HydraSdrTunerController.LNA_GAIN_MAX, HydraSdrTunerController.LNA_GAIN_MIN);
+ mLNAGainSlider.setEnabled(false);
+ mLNAGainSlider.setMajorTickSpacing(1);
+ mLNAGainSlider.setPaintTicks(true);
+ mLNAGainSlider.addChangeListener(event ->
+ {
+ int gain = mLNAGainSlider.getValue();
+
+ if(hasTuner() && !isLoading())
+ {
+ try
+ {
+ getTuner().getController().setLNAGain(gain);
+ save();
+ }
+ catch(Exception e)
+ {
+ mLog.error("Couldn't set HydraSDR LNA gain to:" + gain, e);
+ JOptionPane.showMessageDialog(mLNAGainSlider, "Couldn't set LNA gain value to " + gain);
+ }
+ }
+
+ getLNAGainValueLabel().setText(String.valueOf(gain));
+ });
+ }
+
+ return mLNAGainSlider;
+ }
+
+ private JLabel getMixerGainValueLabel()
+ {
+ if(mMixerGainValueLabel == null)
+ {
+ mMixerGainValueLabel = new JLabel("0");
+ mMixerGainValueLabel.setEnabled(false);
+ }
+
+ return mMixerGainValueLabel;
+ }
+
+ private JSlider getMixerGainSlider()
+ {
+ if(mMixerGainSlider == null)
+ {
+ mMixerGainSlider = new JSlider(JSlider.HORIZONTAL, HydraSdrTunerController.MIXER_GAIN_MIN,
+ HydraSdrTunerController.MIXER_GAIN_MAX, HydraSdrTunerController.MIXER_GAIN_MIN);
+ mMixerGainSlider.setEnabled(false);
+ mMixerGainSlider.setMajorTickSpacing(1);
+ mMixerGainSlider.setPaintTicks(true);
+ mMixerGainSlider.addChangeListener(new ChangeListener()
+ {
+ @Override
+ public void stateChanged(ChangeEvent event)
+ {
+ int gain = mMixerGainSlider.getValue();
+
+ if(hasTuner() && !isLoading())
+ {
+ try
+ {
+ getTuner().getController().setMixerGain(gain);
+ save();
+ }
+ catch(Exception e)
+ {
+ mLog.error("Couldn't set HydraSDR Mixer gain to:" + gain, e);
+ JOptionPane.showMessageDialog(mMixerGainSlider, "Couldn't set Mixer gain value to " + gain);
+ }
+ }
+
+ getMixerGainValueLabel().setText(String.valueOf(gain));
+ }
+ });
+ }
+
+ return mMixerGainSlider;
+ }
+
+ private JLabel getIFGainLabel()
+ {
+ if(mIFGainLabel == null)
+ {
+ mIFGainLabel = new JLabel("IF:");
+ }
+
+ return mIFGainLabel;
+ }
+
+ private JLabel getIFGainValueLabel()
+ {
+ if(mIFGainValueLabel == null)
+ {
+ mIFGainValueLabel = new JLabel("0");
+ mIFGainValueLabel.setEnabled(false);
+ }
+
+ return mIFGainValueLabel;
+ }
+
+ private JSlider getIFGainSlider()
+ {
+ if(mIFGainSlider == null)
+ {
+ mIFGainSlider = new JSlider(JSlider.HORIZONTAL, HydraSdrTunerController.IF_GAIN_MIN,
+ HydraSdrTunerController.IF_GAIN_MAX, HydraSdrTunerController.IF_GAIN_MIN);
+ mIFGainSlider.setEnabled(false);
+ mIFGainSlider.setMajorTickSpacing(1);
+ mIFGainSlider.setPaintTicks(true);
+ mIFGainSlider.addChangeListener(event ->
+ {
+ int gain = mIFGainSlider.getValue();
+
+ if(hasTuner() && !isLoading())
+ {
+ try
+ {
+ getTuner().getController().setIFGain(gain);
+ save();
+ }
+ catch(Exception e)
+ {
+ mLog.error("Couldn't set HydraSDR IF gain to:" + gain, e);
+ JOptionPane.showMessageDialog(mIFGainSlider, "Couldn't set IF gain value to " + gain);
+ }
+ }
+
+ getIFGainValueLabel().setText(String.valueOf(gain));
+ });
+ }
+
+ return mIFGainSlider;
+ }
+
+ private JLabel getMasterGainLabel()
+ {
+ if(mMasterGainLabel == null)
+ {
+ mMasterGainLabel = new JLabel("Master:");
+ }
+
+ return mMasterGainLabel;
+ }
+
+ private JLabel getMasterGainValueLabel()
+ {
+ if(mMasterGainValueLabel == null)
+ {
+ mMasterGainValueLabel = new JLabel("0");
+ mMasterGainValueLabel.setEnabled(false);
+ }
+
+ return mMasterGainValueLabel;
+ }
+
+ private JSlider getMasterGainSlider()
+ {
+ if(mMasterGainSlider == null)
+ {
+ mMasterGainSlider = new JSlider(JSlider.HORIZONTAL, HydraSdrTunerController.GAIN_MIN,
+ HydraSdrTunerController.GAIN_MAX, HydraSdrTunerController.GAIN_MIN);
+ mMasterGainSlider.setEnabled(false);
+ mMasterGainSlider.setMajorTickSpacing(1);
+ mMasterGainSlider.setPaintTicks(true);
+
+ mMasterGainSlider.addChangeListener(event ->
+ {
+ GainMode mode = (GainMode)mGainModeCombo.getSelectedItem();
+ int value = mMasterGainSlider.getValue();
+ Gain gain = Gain.getGain(mode, value);
+
+ if(hasTuner() && !isLoading())
+ {
+ try
+ {
+ getTuner().getController().setGain(gain);
+ save();
+ }
+ catch(Exception e)
+ {
+ mLog.error("Couldn't set HydraSDR gain to:" + gain.name(), e);
+ JOptionPane.showMessageDialog(mMasterGainSlider, "Couldn't set gain value to " +
+ gain.getValue());
+ }
+ }
+
+ getMasterGainValueLabel().setText(String.valueOf(value));
+ });
+ }
+
+ return mMasterGainSlider;
+ }
+
+ private JComboBox getGainModeCombo()
+ {
+ if(mGainModeCombo == null)
+ {
+ mGainModeCombo = new JComboBox<>(GainMode.values());
+ mGainModeCombo.setEnabled(false);
+ mGainModeCombo.addActionListener(e ->
+ {
+ if(hasTuner() && !isLoading())
+ {
+ GainMode mode = (GainMode)mGainModeCombo.getSelectedItem();
+ int value = getMasterGainSlider().getValue();
+ Gain gain = Gain.getGain(mode, value);
+ updateGainComponents(gain);
+ save();
+ }
+ });
+ }
+
+ return mGainModeCombo;
+ }
+
+ private JComboBox getSampleRateCombo()
+ {
+ if(mSampleRateCombo == null)
+ {
+ mSampleRateCombo = new JComboBox<>();
+ mSampleRateCombo.setEnabled(false);
+ mSampleRateCombo.addActionListener(new ActionListener()
+ {
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ if(hasTuner() && !isLoading())
+ {
+ HydraSdrSampleRate rate = (HydraSdrSampleRate)mSampleRateCombo.getSelectedItem();
+
+ try
+ {
+ getTuner().getController().setSampleRate(rate);
+
+ //Adjust the min/max values for the sample rate.
+ adjustForSampleRate(rate.getRate());
+
+ save();
+ }
+ catch(Exception e1)
+ {
+ JOptionPane.showMessageDialog(HydraSdrTunerEditor.this,
+ "Couldn't set sample rate to " + rate.getLabel());
+ mLog.error("Error setting HydraSDR sample rate", e1);
+ }
+ }
+ }
+ });
+ }
+
+ return mSampleRateCombo;
+ }
+
+ /**
+ * Hyperlink button that provides tuner information
+ */
+ private JButton getTunerInfoButton()
+ {
+ if(mTunerInfoButton == null)
+ {
+ mTunerInfoButton = new JButton("Info");
+ mTunerInfoButton.setEnabled(false);
+ mTunerInfoButton.addActionListener(e -> JOptionPane.showMessageDialog(HydraSdrTunerEditor.this,
+ getTunerInfo(), "Tuner Info", JOptionPane.INFORMATION_MESSAGE));
+ }
+
+ return mTunerInfoButton;
+ }
+
+ /**
+ * Updates the enabled state of each of the gain controls according to the
+ * specified gain mode. The master gain control is enabled for linearity
+ * and sensitivity and the individual gain controls are disabled, and
+ * vice-versa for custom mode.
+ */
+ private void updateGainComponents(Gain gain)
+ {
+ if(hasTuner() && gain != null)
+ {
+ boolean isCustom = gain.equals(Gain.CUSTOM);
+
+ getGainModeCombo().setEnabled(true);
+ getGainModeCombo().setSelectedItem(gain.getGainMode());
+ getMasterGainLabel().setEnabled(!isCustom);
+ getMasterGainSlider().setEnabled(!isCustom);
+ getMasterGainSlider().setValue(gain.getValue());
+ getMasterGainValueLabel().setEnabled(!isCustom);
+ getIFGainLabel().setEnabled(isCustom);
+ getIFGainSlider().setEnabled(isCustom);
+ getIFGainValueLabel().setEnabled(isCustom);
+ getLNAAGCCheckBox().setEnabled(isCustom);
+ getLNAGainSlider().setEnabled(isCustom && !getConfiguration().isLNAAGC());
+ getLNAGainValueLabel().setEnabled(isCustom);
+ getMixerAGCCheckBox().setEnabled(isCustom);
+ getMixerGainSlider().setEnabled(isCustom && !getConfiguration().isMixerAGC());
+ getMixerGainValueLabel().setEnabled(isCustom);
+ if(isCustom)
+ {
+ getIFGainSlider().setValue(getConfiguration().getIFGain());
+ getLNAGainSlider().setValue(getConfiguration().getLNAGain());
+ getMixerGainSlider().setValue(getConfiguration().getMixerGain());
+ getMixerAGCCheckBox().setSelected(getConfiguration().isMixerAGC());
+ getLNAAGCCheckBox().setSelected(getConfiguration().isLNAAGC());
+ }
+ else
+ {
+ getIFGainSlider().setValue(0);
+ getLNAGainSlider().setValue(0);
+ getMixerGainSlider().setValue(0);
+ getMixerAGCCheckBox().setSelected(false);
+ getLNAAGCCheckBox().setSelected(false);
+ }
+ }
+ else
+ {
+ getGainModeCombo().setEnabled(false);
+ getMasterGainLabel().setEnabled(false);
+ getMasterGainSlider().setEnabled(false);
+ getMasterGainSlider().setValue(0);
+ getMasterGainValueLabel().setEnabled(false);
+ getIFGainLabel().setEnabled(false);
+ getIFGainSlider().setEnabled(false);
+ getIFGainSlider().setValue(0);
+ getIFGainValueLabel().setEnabled(false);
+ getLNAAGCCheckBox().setEnabled(false);
+ getLNAAGCCheckBox().setSelected(false);
+ getLNAGainSlider().setEnabled(false);
+ getLNAGainSlider().setValue(0);
+ getLNAGainValueLabel().setEnabled(false);
+ getMixerAGCCheckBox().setEnabled(false);
+ getMixerAGCCheckBox().setSelected(false);
+ getMixerGainSlider().setEnabled(false);
+ getMixerGainSlider().setValue(0);
+ getMixerGainValueLabel().setEnabled(false);
+ }
+ }
+
+ @Override
+ public void save()
+ {
+ if(hasConfiguration() && !isLoading())
+ {
+ getConfiguration().setFrequency(getFrequencyControl().getFrequency());
+ getConfiguration().setMinimumFrequency(getMinimumFrequencyTextField().getFrequency());
+ getConfiguration().setMaximumFrequency(getMaximumFrequencyTextField().getFrequency());
+ double value = ((SpinnerNumberModel) getFrequencyCorrectionSpinner().getModel()).getNumber().doubleValue();
+ getConfiguration().setFrequencyCorrection(value);
+ getConfiguration().setAutoPPMCorrectionEnabled(getAutoPPMCheckBox().isSelected());
+ getConfiguration().setSampleRate(((HydraSdrSampleRate)getSampleRateCombo().getSelectedItem()).getRate());
+ Gain gain = Gain.getGain((GainMode)mGainModeCombo.getSelectedItem(), getMasterGainSlider().getValue());
+ getConfiguration().setGain(gain);
+ getConfiguration().setIFGain(getIFGainSlider().getValue());
+ getConfiguration().setMixerGain(getMixerGainSlider().getValue());
+ getConfiguration().setLNAGain(getLNAGainSlider().getValue());
+ getConfiguration().setMixerAGC(getMixerAGCCheckBox().isSelected());
+ getConfiguration().setLNAAGC(getLNAAGCCheckBox().isSelected());
+ getConfiguration().setBiasT(getBiasTCheckBox().isSelected());
+ saveConfiguration();
+ }
+ }
+
+ /**
+ * Finds the HydraSDR sample rate entry that matches the value.
+ * @param value in Hertz
+ * @return the matching rate entry or null.
+ */
+ private HydraSdrSampleRate getSampleRate(int value)
+ {
+ if(hasTuner())
+ {
+ List rates = getTuner().getController().getSampleRates();
+
+ for(HydraSdrSampleRate rate : rates)
+ {
+ if(rate.getRate() == value)
+ {
+ return rate;
+ }
+ }
+
+ if(rates.size() > 0)
+ {
+ return rates.get(0);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Updates the sample rate tooltip according to the tuner controller's lock state.
+ */
+ private void updateSampleRateToolTip()
+ {
+ if(hasTuner() && getTuner().getController().isLockedSampleRate())
+ {
+ getSampleRateCombo().setToolTipText("Sample Rate is locked. Disable decoding channels to unlock.");
+ }
+ else
+ {
+ getSampleRateCombo().setToolTipText("Select a sample rate for the tuner");
+ }
+ }
+
+ @Override
+ public void setTunerLockState(boolean locked)
+ {
+ getFrequencyPanel().updateControls();
+ getSampleRateCombo().setEnabled(!locked);
+ updateSampleRateToolTip();
+ }
+
+ private String getTunerInfo()
+ {
+ if(getDiscoveredTuner().getTunerStatus() == TunerStatus.ERROR)
+ {
+ return getDiscoveredTuner().getErrorMessage();
+ }
+
+ if(hasTuner())
+ {
+ StringBuilder sb = new StringBuilder();
+
+ HydraSdrDeviceInformation info = getTuner().getController().getDeviceInfo();
+
+ sb.append("HydraSDR Tuner
");
+ sb.append("Serial: ");
+ sb.append(info.getSerialNumber());
+ sb.append("
");
+
+ sb.append("Firmware: ");
+ String[] firmware = info.getVersion().split(" ");
+ sb.append(firmware.length > 1 ? firmware[0] : info.getVersion());
+ sb.append("
");
+
+ sb.append("Part: ");
+ sb.append(info.getPartNumber());
+ sb.append("
");
+
+ sb.append("Board ID: ");
+ sb.append(info.getBoardID().getLabel());
+ sb.append("
");
+
+ return sb.toString();
+ }
+
+ return null;
+ }
+}
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 f7501227..ed6c91cc 100644
--- a/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java
+++ b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java
@@ -22,9 +22,6 @@ package io.github.dsheirer.vector.calibrate;
import io.github.dsheirer.dsp.am.AmDemodulatorCalibration;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.calibration.VectorCalibrationPreference;
-import io.github.dsheirer.vector.calibrate.airspy.AirspySampleConverterCalibration;
-import io.github.dsheirer.vector.calibrate.airspy.AirspyUnpackedCalibration;
-import io.github.dsheirer.vector.calibrate.airspy.AirspyUnpackedInterleavedCalibration;
import io.github.dsheirer.vector.calibrate.demodulator.DifferentialDemodulatorCalibration;
import io.github.dsheirer.vector.calibrate.demodulator.FmDemodulatorCalibration;
import io.github.dsheirer.vector.calibrate.filter.FirFilterCalibration;
@@ -41,6 +38,9 @@ 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.P25P1SoftSyncCalibration;
import io.github.dsheirer.vector.calibrate.window.WindowCalibration;
@@ -100,9 +100,9 @@ public class CalibrationManager
sInstance = new CalibrationManager();
- sInstance.add(new AirspySampleConverterCalibration());
- sInstance.add(new AirspyUnpackedCalibration());
- sInstance.add(new AirspyUnpackedInterleavedCalibration());
+ sInstance.add(new PackedSampleConverterCalibration());
+ sInstance.add(new UnpackedSampleConverterCalibration());
+ sInstance.add(new UnpackedInterleavedSampleConverterCalibration());
sInstance.add(new AmDemodulatorCalibration());
sInstance.add(new ComplexGainCalibration());
sInstance.add(new ComplexGainControlCalibration());
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 fab025d2..f2e70d65 100644
--- a/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationType.java
+++ b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationType.java
@@ -32,9 +32,6 @@ public enum CalibrationType
OSCILLATOR_COMPLEX("Complex Oscillator", 1),
GAIN_COMPLEX("Complex Gain", 1),
- AIRSPY_SAMPLE_CONVERTER("Airspy Sample Converter", 1),
- AIRSPY_UNPACKED_INTERLEAVED_ITERATOR("Airspy Unpacked Interleaved Iterator", 1),
- AIRSPY_UNPACKED_ITERATOR("Airspy Unpacked Iterator", 1),
AM_DEMODULATOR("AM Demodulator", 1),
DC_REMOVAL_REAL("Real DC Removal Filter", 1),
DMR_SOFT_SYNC_DETECTOR("DMR Soft Sync Detector", 1),
@@ -53,6 +50,9 @@ public enum CalibrationType
MIXER_COMPLEX("Complex Mixer", 1),
OSCILLATOR_REAL("Real Oscillator", 1),
P25P1_SOFT_SYNC_DETECTOR("P25 Phase 1 Soft Sync Detector", 1),
+ SAMPLE_PACKED_CONVERTER("Packed Sample Converter", 1),
+ SAMPLE_UNPACKED_INTERLEAVED_ITERATOR("Unpacked Interleaved Sample Iterator", 1),
+ SAMPLE_UNPACKED_ITERATOR("Unpacked Sample Iterator", 1),
SQUELCHING_FM_DEMODULATOR("Squelching FM Demodulator", 1),
WINDOW("Window", 1);
diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspySampleConverterCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/sample/PackedSampleConverterCalibration.java
similarity index 81%
rename from src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspySampleConverterCalibration.java
rename to src/main/java/io/github/dsheirer/vector/calibrate/sample/PackedSampleConverterCalibration.java
index 9dae658c..4eceb7f8 100644
--- a/src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspySampleConverterCalibration.java
+++ b/src/main/java/io/github/dsheirer/vector/calibrate/sample/PackedSampleConverterCalibration.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * 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
@@ -17,19 +17,18 @@
* ****************************************************************************
*/
-package io.github.dsheirer.vector.calibrate.airspy;
+package io.github.dsheirer.vector.calibrate.sample;
-import io.github.dsheirer.buffer.airspy.ScalarUnpackedSampleConverter;
-import io.github.dsheirer.buffer.airspy.VectorUnpackedSampleConverter;
+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;
-import java.nio.ByteBuffer;
-
-public class AirspySampleConverterCalibration extends Calibration
+public class PackedSampleConverterCalibration extends Calibration
{
private static final int BUFFER_SIZE = 262144;
private static final int ITERATION_DURATION_MS = 1000;
@@ -39,9 +38,9 @@ public class AirspySampleConverterCalibration extends Calibration
/**
* Constructs an instance
*/
- public AirspySampleConverterCalibration()
+ public PackedSampleConverterCalibration()
{
- super(CalibrationType.AIRSPY_SAMPLE_CONVERTER);
+ super(CalibrationType.SAMPLE_PACKED_CONVERTER);
}
@Override
@@ -59,7 +58,7 @@ public class AirspySampleConverterCalibration extends Calibration
scalarMean.increment(score);
}
- mLog.info("AIRSPY CONVERTER WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
+ mLog.info("PACKED SAMPLE CONVERTER WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
for(int vectorTest = 0; vectorTest < WARM_UP_ITERATIONS; vectorTest++)
{
@@ -67,7 +66,7 @@ public class AirspySampleConverterCalibration extends Calibration
vectorMean.increment(score);
}
- mLog.info("AIRSPY CONVERTER WARMUP - VECTOR: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
+ mLog.info("PACKED SAMPLE CONVERTER WARMUP - VECTOR: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
scalarMean.clear();
vectorMean.clear();
@@ -78,7 +77,7 @@ public class AirspySampleConverterCalibration extends Calibration
scalarMean.increment(score);
}
- mLog.info("AIRSPY CONVERTER - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
+ mLog.info("PACKED SAMPLE CONVERTER - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
for(int vectorTest = 0; vectorTest < TEST_ITERATIONS; vectorTest++)
{
@@ -86,7 +85,7 @@ public class AirspySampleConverterCalibration extends Calibration
vectorMean.increment(score);
}
- mLog.info("AIRSPY CONVERTER - VECTOR: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
+ mLog.info("PACKED SAMPLE CONVERTER - VECTOR: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
if(scalarMean.getResult() > vectorMean.getResult())
{
@@ -97,7 +96,7 @@ public class AirspySampleConverterCalibration extends Calibration
setImplementation(Implementation.VECTOR_SIMD_PREFERRED);
}
- mLog.info("AIRSPY CONVERTER - SETTING OPTIMAL IMPLEMENTATION TO: " + getImplementation());
+ mLog.info("PACKED SAMPLE CONVERTER - SETTING OPTIMAL IMPLEMENTATION TO: " + getImplementation());
}
private long testScalar(ByteBuffer buffer)
diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspyUnpackedInterleavedCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/sample/UnpackedInterleavedSampleConverterCalibration.java
similarity index 76%
rename from src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspyUnpackedInterleavedCalibration.java
rename to src/main/java/io/github/dsheirer/vector/calibrate/sample/UnpackedInterleavedSampleConverterCalibration.java
index daf7accf..26f202df 100644
--- a/src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspyUnpackedInterleavedCalibration.java
+++ b/src/main/java/io/github/dsheirer/vector/calibrate/sample/UnpackedInterleavedSampleConverterCalibration.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * 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
@@ -17,14 +17,14 @@
* ****************************************************************************
*/
-package io.github.dsheirer.vector.calibrate.airspy;
+package io.github.dsheirer.vector.calibrate.sample;
-import io.github.dsheirer.buffer.airspy.AirspyBufferIterator;
-import io.github.dsheirer.buffer.airspy.AirspyInterleavedBufferIteratorScalar;
-import io.github.dsheirer.buffer.airspy.AirspyInterleavedBufferIteratorVector128Bits;
-import io.github.dsheirer.buffer.airspy.AirspyInterleavedBufferIteratorVector256Bits;
-import io.github.dsheirer.buffer.airspy.AirspyInterleavedBufferIteratorVector512Bits;
-import io.github.dsheirer.buffer.airspy.AirspyInterleavedBufferIteratorVector64Bits;
+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;
@@ -33,9 +33,9 @@ import jdk.incubator.vector.FloatVector;
import org.apache.commons.math3.stat.descriptive.moment.Mean;
/**
- * Calculates optimal implementation (SCALAR vs VECTOR) for interleaved airspy native buffers.
+ * Calculates optimal implementation (SCALAR vs VECTOR) for interleaved sample native buffers.
*/
-public class AirspyUnpackedInterleavedCalibration extends Calibration
+public class UnpackedInterleavedSampleConverterCalibration extends Calibration
{
private static final int BUFFER_SIZE = 131072;
private static final int ITERATION_DURATION_MS = 1000;
@@ -45,20 +45,20 @@ public class AirspyUnpackedInterleavedCalibration extends Calibration
/**
* Constructs an instance
*/
- public AirspyUnpackedInterleavedCalibration()
+ public UnpackedInterleavedSampleConverterCalibration()
{
- super(CalibrationType.AIRSPY_UNPACKED_INTERLEAVED_ITERATOR);
+ super(CalibrationType.SAMPLE_UNPACKED_INTERLEAVED_ITERATOR);
}
@Override
public void calibrate() throws CalibrationException
{
short[] samples = getShortSamples(BUFFER_SIZE);
- short[] residualI = getShortSamples(AirspyBufferIterator.I_OVERLAP);
- short[] residualQ = getShortSamples(AirspyBufferIterator.Q_OVERLAP);
+ short[] residualI = getShortSamples(SampleBufferIterator.I_OVERLAP);
+ short[] residualQ = getShortSamples(SampleBufferIterator.Q_OVERLAP);
//Warm-Up Phase ....
- mLog.info("AIRSPY UNPACKED INTERLEAVED - VECTOR SIMD LANES PREFERRED: " + FloatVector.SPECIES_PREFERRED.length());
+ mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER - VECTOR SIMD LANES PREFERRED: " + FloatVector.SPECIES_PREFERRED.length());
Mean scalarMean = new Mean();
@@ -68,7 +68,7 @@ public class AirspyUnpackedInterleavedCalibration extends Calibration
scalarMean.increment(score);
}
- mLog.info("AIRSPY UNPACKED INTERLEAVED WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
+ mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
switch(FloatVector.SPECIES_PREFERRED.length())
{
@@ -84,7 +84,7 @@ public class AirspyUnpackedInterleavedCalibration extends Calibration
vectorMean.increment(score);
}
- mLog.info("AIRSPY UNPACKED INTERLEAVED WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
+ mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
}
case 8:
{
@@ -96,7 +96,7 @@ public class AirspyUnpackedInterleavedCalibration extends Calibration
vectorMean.increment(score);
}
- mLog.info("AIRSPY UNPACKED INTERLEAVED WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
+ mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
}
case 4:
{
@@ -108,7 +108,7 @@ public class AirspyUnpackedInterleavedCalibration extends Calibration
vectorMean.increment(score);
}
- mLog.info("AIRSPY UNPACKED INTERLEAVED WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
+ mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
}
case 2:
{
@@ -120,7 +120,7 @@ public class AirspyUnpackedInterleavedCalibration extends Calibration
vectorMean.increment(score);
}
- mLog.info("AIRSPY UNPACKED INTERLEAVED WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
+ mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
}
//Test Phase ....
@@ -134,7 +134,7 @@ public class AirspyUnpackedInterleavedCalibration extends Calibration
double bestScore = scalarMean.getResult();
setImplementation(Implementation.SCALAR);
- mLog.info("AIRSPY UNPACKED INTERLEAVED - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
+ mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
switch(FloatVector.SPECIES_PREFERRED.length())
{
@@ -156,7 +156,7 @@ public class AirspyUnpackedInterleavedCalibration extends Calibration
setImplementation(Implementation.VECTOR_SIMD_512);
}
- mLog.info("AIRSPY UNPACKED INTERLEAVED - VECTOR 512: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
+ mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER - VECTOR 512: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
}
case 8:
{
@@ -174,7 +174,7 @@ public class AirspyUnpackedInterleavedCalibration extends Calibration
setImplementation(Implementation.VECTOR_SIMD_256);
}
- mLog.info("AIRSPY UNPACKED INTERLEAVED - VECTOR 256: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
+ mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER - VECTOR 256: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
}
case 4:
{
@@ -192,7 +192,7 @@ public class AirspyUnpackedInterleavedCalibration extends Calibration
setImplementation(Implementation.VECTOR_SIMD_128);
}
- mLog.info("AIRSPY UNPACKED INTERLEAVED - VECTOR 128: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
+ mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER - VECTOR 128: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
}
case 2:
{
@@ -209,12 +209,12 @@ public class AirspyUnpackedInterleavedCalibration extends Calibration
setImplementation(Implementation.VECTOR_SIMD_64);
}
- mLog.info("AIRSPY UNPACKED INTERLEAVED - VECTOR 64: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
+ mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER - VECTOR 64: " + DECIMAL_FORMAT.format(vectorMean.getResult()));
}
}
}
- mLog.info("AIRSPY UNPACKED INTERLEAVED - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation());
+ mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation());
}
private long calibrateScalar(short[] samples, short[] residualI, short[] residualQ)
@@ -224,7 +224,7 @@ public class AirspyUnpackedInterleavedCalibration extends Calibration
long count = 0;
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
- AirspyInterleavedBufferIteratorScalar iterator = new AirspyInterleavedBufferIteratorScalar(samples, residualI,
+ SampleInterleavedBufferIteratorScalar iterator = new SampleInterleavedBufferIteratorScalar(samples, residualI,
residualQ, 0.0f, System.currentTimeMillis(), 0.0f);
while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS))
@@ -245,7 +245,7 @@ public class AirspyUnpackedInterleavedCalibration extends Calibration
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
- AirspyInterleavedBufferIteratorVector64Bits iterator = new AirspyInterleavedBufferIteratorVector64Bits(samples, residualI,
+ SampleInterleavedBufferIteratorVector64Bits iterator = new SampleInterleavedBufferIteratorVector64Bits(samples, residualI,
residualQ, 0.0f, System.currentTimeMillis(), 0.0f);
while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS))
@@ -266,7 +266,7 @@ public class AirspyUnpackedInterleavedCalibration extends Calibration
for(int x = 0; x < ITERATION_DURATION_MS; x++)
{
- AirspyInterleavedBufferIteratorVector128Bits iterator = new AirspyInterleavedBufferIteratorVector128Bits(samples, residualI,
+ SampleInterleavedBufferIteratorVector128Bits iterator = new SampleInterleavedBufferIteratorVector128Bits(samples, residualI,
residualQ, 0.0f, System.currentTimeMillis(), 0.0f);
while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS))
@@ -287,8 +287,8 @@ public class AirspyUnpackedInterleavedCalibration extends Calibration
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
- AirspyInterleavedBufferIteratorVector256Bits iterator =
- new AirspyInterleavedBufferIteratorVector256Bits(samples, residualI, residualQ, 0.0f, System.currentTimeMillis(), 0.0f);
+ SampleInterleavedBufferIteratorVector256Bits iterator =
+ new SampleInterleavedBufferIteratorVector256Bits(samples, residualI, residualQ, 0.0f, System.currentTimeMillis(), 0.0f);
while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS))
{
@@ -308,8 +308,8 @@ public class AirspyUnpackedInterleavedCalibration extends Calibration
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
- AirspyInterleavedBufferIteratorVector512Bits iterator =
- new AirspyInterleavedBufferIteratorVector512Bits(samples, residualI, residualQ, 0.0f,
+ SampleInterleavedBufferIteratorVector512Bits iterator =
+ new SampleInterleavedBufferIteratorVector512Bits(samples, residualI, residualQ, 0.0f,
System.currentTimeMillis(), 0.0f);
while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS))
diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspyUnpackedCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/sample/UnpackedSampleConverterCalibration.java
similarity index 77%
rename from src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspyUnpackedCalibration.java
rename to src/main/java/io/github/dsheirer/vector/calibrate/sample/UnpackedSampleConverterCalibration.java
index e0d2093d..2fa848d3 100644
--- a/src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspyUnpackedCalibration.java
+++ b/src/main/java/io/github/dsheirer/vector/calibrate/sample/UnpackedSampleConverterCalibration.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * 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
@@ -17,14 +17,14 @@
* ****************************************************************************
*/
-package io.github.dsheirer.vector.calibrate.airspy;
+package io.github.dsheirer.vector.calibrate.sample;
-import io.github.dsheirer.buffer.airspy.AirspyBufferIterator;
-import io.github.dsheirer.buffer.airspy.AirspyBufferIteratorScalar;
-import io.github.dsheirer.buffer.airspy.AirspyBufferIteratorVector128Bits;
-import io.github.dsheirer.buffer.airspy.AirspyBufferIteratorVector256Bits;
-import io.github.dsheirer.buffer.airspy.AirspyBufferIteratorVector512Bits;
-import io.github.dsheirer.buffer.airspy.AirspyBufferIteratorVector64Bits;
+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;
@@ -33,9 +33,9 @@ import jdk.incubator.vector.FloatVector;
import org.apache.commons.math3.stat.descriptive.moment.Mean;
/**
- * Calculates optimal implementation (SCALAR vs VECTOR) for non-interleaved airspy native buffers.
+ * Calculates optimal implementation (SCALAR vs VECTOR) for non-interleaved sample native buffers.
*/
-public class AirspyUnpackedCalibration extends Calibration
+public class UnpackedSampleConverterCalibration extends Calibration
{
private static final int BUFFER_SIZE = 131072;
private static final int ITERATION_DURATION_MS = 1000;
@@ -45,19 +45,19 @@ public class AirspyUnpackedCalibration extends Calibration
/**
* Constructs an instance
*/
- public AirspyUnpackedCalibration()
+ public UnpackedSampleConverterCalibration()
{
- super(CalibrationType.AIRSPY_UNPACKED_ITERATOR);
+ super(CalibrationType.SAMPLE_UNPACKED_ITERATOR);
}
@Override
public void calibrate() throws CalibrationException
{
short[] samples = getShortSamples(BUFFER_SIZE);
- short[] residualI = getShortSamples(AirspyBufferIterator.I_OVERLAP);
- short[] residualQ = getShortSamples(AirspyBufferIterator.Q_OVERLAP);
+ short[] residualI = getShortSamples(SampleBufferIterator.I_OVERLAP);
+ short[] residualQ = getShortSamples(SampleBufferIterator.Q_OVERLAP);
- mLog.info("AIRSPY UNPACKED - VECTOR SIMD LANES PREFERRED: " + FloatVector.SPECIES_PREFERRED.length());
+ mLog.info("UNPACKED SAMPLE CONVERTER - VECTOR SIMD LANES PREFERRED: " + FloatVector.SPECIES_PREFERRED.length());
//Warm-Up Phase ....
Mean scalarMean = new Mean();
@@ -68,7 +68,7 @@ public class AirspyUnpackedCalibration extends Calibration
scalarMean.increment(score);
}
- mLog.info("AIRSPY UNPACKED WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
+ mLog.info("UNPACKED SAMPLE CONVERTER WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
switch(FloatVector.SPECIES_PREFERRED.length())
{
@@ -83,7 +83,7 @@ public class AirspyUnpackedCalibration extends Calibration
vector512Mean.increment(score);
}
- mLog.info("AIRSPY UNPACKED WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult()));
+ mLog.info("UNPACKED SAMPLE CONVERTER WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult()));
}
case 8:
{
@@ -94,7 +94,7 @@ public class AirspyUnpackedCalibration extends Calibration
vector256Mean.increment(score);
}
- mLog.info("AIRSPY UNPACKED WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult()));
+ mLog.info("UNPACKED SAMPLE CONVERTER WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult()));
}
case 4:
{
@@ -105,7 +105,7 @@ public class AirspyUnpackedCalibration extends Calibration
vector128Mean.increment(score);
}
- mLog.info("AIRSPY UNPACKED WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult()));
+ mLog.info("UNPACKED SAMPLE CONVERTER WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult()));
}
case 2:
{
@@ -116,7 +116,7 @@ public class AirspyUnpackedCalibration extends Calibration
vector64Mean.increment(score);
}
- mLog.info("AIRSPY UNPACKED WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult()));
+ mLog.info("UNPACKED SAMPLE CONVERTER WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult()));
}
}
@@ -131,7 +131,7 @@ public class AirspyUnpackedCalibration extends Calibration
double bestScore = scalarMean.getResult();
setImplementation(Implementation.SCALAR);
- mLog.info("AIRSPY UNPACKED - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
+ mLog.info("UNPACKED SAMPLE CONVERTER - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
switch(FloatVector.SPECIES_PREFERRED.length())
{
@@ -146,7 +146,7 @@ public class AirspyUnpackedCalibration extends Calibration
vector512Mean.increment(score);
}
- mLog.info("AIRSPY UNPACKED - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult()));
+ mLog.info("UNPACKED SAMPLE CONVERTER - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult()));
if(vector512Mean.getResult() > bestScore)
{
bestScore = vector512Mean.getResult();
@@ -162,7 +162,7 @@ public class AirspyUnpackedCalibration extends Calibration
vector256Mean.increment(score);
}
- mLog.info("AIRSPY UNPACKED - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult()));
+ mLog.info("UNPACKED SAMPLE CONVERTER - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult()));
if(vector256Mean.getResult() > bestScore)
{
bestScore = vector256Mean.getResult();
@@ -178,7 +178,7 @@ public class AirspyUnpackedCalibration extends Calibration
vector128Mean.increment(score);
}
- mLog.info("AIRSPY UNPACKED - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult()));
+ mLog.info("UNPACKED SAMPLE CONVERTER - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult()));
if(vector128Mean.getResult() > bestScore)
{
bestScore = vector128Mean.getResult();
@@ -194,7 +194,7 @@ public class AirspyUnpackedCalibration extends Calibration
vector64Mean.increment(score);
}
- mLog.info("AIRSPY UNPACKED - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult()));
+ mLog.info("UNPACKED SAMPLE CONVERTER - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult()));
if(vector64Mean.getResult() > bestScore)
{
setImplementation(Implementation.VECTOR_SIMD_64);
@@ -202,7 +202,7 @@ public class AirspyUnpackedCalibration extends Calibration
}
}
- mLog.info("AIRSPY UNPACKED - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation());
+ mLog.info("UNPACKED SAMPLE CONVERTER - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation());
}
private long testScalar(short[] samples, short[] residualI, short[] residualQ)
@@ -213,7 +213,7 @@ public class AirspyUnpackedCalibration extends Calibration
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
- AirspyBufferIteratorScalar iterator = new AirspyBufferIteratorScalar(samples, residualI,
+ SampleBufferIteratorScalar iterator = new SampleBufferIteratorScalar(samples, residualI,
residualQ, 0.0f, System.currentTimeMillis(), 0.0f);
while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS))
@@ -234,7 +234,7 @@ public class AirspyUnpackedCalibration extends Calibration
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
- AirspyBufferIteratorVector64Bits iterator = new AirspyBufferIteratorVector64Bits(samples, residualI,
+ SampleBufferIteratorVector64Bits iterator = new SampleBufferIteratorVector64Bits(samples, residualI,
residualQ, 0.0f, System.currentTimeMillis(), 0.0f);
while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS))
@@ -254,7 +254,7 @@ public class AirspyUnpackedCalibration extends Calibration
long count = 0;
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
- AirspyBufferIteratorVector128Bits iterator = new AirspyBufferIteratorVector128Bits(samples, residualI,
+ SampleBufferIteratorVector128Bits iterator = new SampleBufferIteratorVector128Bits(samples, residualI,
residualQ, 0.0f, System.currentTimeMillis(), 0.0f);
while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS))
@@ -275,8 +275,8 @@ public class AirspyUnpackedCalibration extends Calibration
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
- AirspyBufferIteratorVector256Bits iterator =
- new AirspyBufferIteratorVector256Bits(samples, residualI, residualQ, 0.0f,
+ SampleBufferIteratorVector256Bits iterator =
+ new SampleBufferIteratorVector256Bits(samples, residualI, residualQ, 0.0f,
System.currentTimeMillis(), 0.0f);
while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS))
@@ -297,8 +297,8 @@ public class AirspyUnpackedCalibration extends Calibration
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
- AirspyBufferIteratorVector512Bits iterator =
- new AirspyBufferIteratorVector512Bits(samples, residualI, residualQ, 0.0f,
+ SampleBufferIteratorVector512Bits iterator =
+ new SampleBufferIteratorVector512Bits(samples, residualI, residualQ, 0.0f,
System.currentTimeMillis(), 0.0f);
while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS))
diff --git a/src/main/resources/52-hydrasdr.rules b/src/main/resources/52-hydrasdr.rules
new file mode 100644
index 00000000..23651fbe
--- /dev/null
+++ b/src/main/resources/52-hydrasdr.rules
@@ -0,0 +1,11 @@
+# HydraSDR RFOne udev rules - Universal cross-distribution compatibility
+# Works on: Ubuntu, Debian, Fedora, AlmaLinux, openSUSE, Arch Linux, and more
+# Supports: systemd (modern) and legacy systems
+# Note: Due to rule ordering, the fallback GROUP="users" overrides the primary GROUP="plugdev" even on distros where plugdev exists.
+# This doesn't break functionality thanks to uaccess tags, but for stricter security, consider revising to a single rule per device with GROUP="root", MODE="0660", and TAG+="uaccess" to rely solely on ACLs.
+
+# HydraSDR RFOne main device (idVendor=38af VERNOUX BENJAMIN, idProduct=0001 HydraSDR RFOne Normal operation mode)
+# Primary rule for distributions with plugdev group (Debian/Ubuntu family)
+SUBSYSTEM=="usb", ATTR{idVendor}=="38af", ATTR{idProduct}=="0001", SYMLINK+="hydrasdr_rfone-%k", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl"
+# Fallback rule for distributions without plugdev group (Red Hat/SUSE/Arch family)
+SUBSYSTEM=="usb", ATTR{idVendor}=="38af", ATTR{idProduct}=="0001", SYMLINK+="hydrasdr_rfone-%k", MODE="0664", GROUP="users", TAG+="uaccess"