From d8e2e0ac57989c23242d232c1ad5663ee39f49f0 Mon Sep 17 00:00:00 2001 From: Denny Sheirer Date: Thu, 25 Dec 2025 04:18:12 -0500 Subject: [PATCH] Hydrasdr Tuner Support (#2339) HydraSDR tuner support * Adds Bias-T option * Update nightly.yml * Fix BoardID * Consolidated calibrations and sample converters and removed logging. --------- Co-authored-by: Benjamin Vernoux Co-authored-by: denny --- .github/workflows/nightly.yml | 9 +- .../airspy/{hf => }/AirspyHfNativeBuffer.java | 4 +- .../{hf => }/AirspyHfNativeBufferFactory.java | 4 +- .../hydrasdr/HydraSdrBufferIterator.java | 119 ++ .../HydraSdrBufferIteratorScalar.java | 98 ++ .../HydraSdrBufferIteratorVector128Bits.java | 128 ++ .../HydraSdrBufferIteratorVector256Bits.java | 122 ++ .../HydraSdrBufferIteratorVector512Bits.java | 125 ++ .../HydraSdrBufferIteratorVector64Bits.java | 141 +++ ...draSdrInterleavedBufferIteratorScalar.java | 96 ++ ...nterleavedBufferIteratorVector128Bits.java | 125 ++ ...nterleavedBufferIteratorVector256Bits.java | 119 ++ ...nterleavedBufferIteratorVector512Bits.java | 122 ++ ...InterleavedBufferIteratorVector64Bits.java | 137 +++ .../IHydraSdrSampleConverter.java} | 10 +- .../hydrasdr/ScalarPackedSampleConverter.java | 94 ++ .../ScalarUnpackedSampleConverter.java | 8 +- .../VectorUnpackedSampleConverter.java | 8 +- .../buffer/sample/ISampleConverter.java | 40 + .../SampleBufferIterator.java} | 10 +- .../SampleBufferIteratorScalar.java} | 12 +- .../SampleBufferIteratorVector128Bits.java} | 12 +- .../SampleBufferIteratorVector256Bits.java} | 12 +- .../SampleBufferIteratorVector512Bits.java} | 12 +- .../SampleBufferIteratorVector64Bits.java} | 12 +- ...ampleInterleavedBufferIteratorScalar.java} | 12 +- ...terleavedBufferIteratorVector128Bits.java} | 12 +- ...terleavedBufferIteratorVector256Bits.java} | 12 +- ...terleavedBufferIteratorVector512Bits.java} | 12 +- ...nterleavedBufferIteratorVector64Bits.java} | 12 +- .../SampleNativeBuffer.java} | 36 +- .../SampleNativeBufferFactory.java} | 30 +- .../ScalarPackedSampleConverter.java | 10 +- .../sample/ScalarUnpackedSampleConverter.java | 83 ++ .../sample/VectorUnpackedSampleConverter.java | 129 ++ .../dsheirer/source/tuner/TunerClass.java | 7 +- .../dsheirer/source/tuner/TunerFactory.java | 10 + .../dsheirer/source/tuner/TunerType.java | 3 +- .../tuner/airspy/AirspyTunerController.java | 6 +- .../airspy/hf/AirspyHfTunerController.java | 4 +- .../configuration/TunerConfiguration.java | 2 + .../hydrasdr/HydraSdrDeviceInformation.java | 157 +++ .../tuner/hydrasdr/HydraSdrSampleRate.java | 35 + .../source/tuner/hydrasdr/HydraSdrTuner.java | 95 ++ .../hydrasdr/HydraSdrTunerConfiguration.java | 144 +++ .../hydrasdr/HydraSdrTunerController.java | 1054 +++++++++++++++++ .../tuner/hydrasdr/HydraSdrTunerEditor.java | 742 ++++++++++++ .../vector/calibrate/CalibrationManager.java | 12 +- .../vector/calibrate/CalibrationType.java | 6 +- .../PackedSampleConverterCalibration.java} | 27 +- ...nterleavedSampleConverterCalibration.java} | 66 +- .../UnpackedSampleConverterCalibration.java} | 66 +- src/main/resources/52-hydrasdr.rules | 11 + 53 files changed, 4153 insertions(+), 221 deletions(-) rename src/main/java/io/github/dsheirer/buffer/airspy/{hf => }/AirspyHfNativeBuffer.java (97%) rename src/main/java/io/github/dsheirer/buffer/airspy/{hf => }/AirspyHfNativeBufferFactory.java (96%) create mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIterator.java create mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIteratorScalar.java create mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIteratorVector128Bits.java create mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIteratorVector256Bits.java create mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIteratorVector512Bits.java create mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIteratorVector64Bits.java create mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrInterleavedBufferIteratorScalar.java create mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrInterleavedBufferIteratorVector128Bits.java create mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrInterleavedBufferIteratorVector256Bits.java create mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrInterleavedBufferIteratorVector512Bits.java create mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrInterleavedBufferIteratorVector64Bits.java rename src/main/java/io/github/dsheirer/buffer/{airspy/IAirspySampleConverter.java => hydrasdr/IHydraSdrSampleConverter.java} (77%) create mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/ScalarPackedSampleConverter.java rename src/main/java/io/github/dsheirer/buffer/{airspy => hydrasdr}/ScalarUnpackedSampleConverter.java (89%) rename src/main/java/io/github/dsheirer/buffer/{airspy => hydrasdr}/VectorUnpackedSampleConverter.java (93%) create mode 100644 src/main/java/io/github/dsheirer/buffer/sample/ISampleConverter.java rename src/main/java/io/github/dsheirer/buffer/{airspy/AirspyBufferIterator.java => sample/SampleBufferIterator.java} (93%) rename src/main/java/io/github/dsheirer/buffer/{airspy/AirspyBufferIteratorScalar.java => sample/SampleBufferIteratorScalar.java} (90%) rename src/main/java/io/github/dsheirer/buffer/{airspy/AirspyBufferIteratorVector128Bits.java => sample/SampleBufferIteratorVector128Bits.java} (93%) rename src/main/java/io/github/dsheirer/buffer/{airspy/AirspyBufferIteratorVector256Bits.java => sample/SampleBufferIteratorVector256Bits.java} (93%) rename src/main/java/io/github/dsheirer/buffer/{airspy/AirspyBufferIteratorVector512Bits.java => sample/SampleBufferIteratorVector512Bits.java} (93%) rename src/main/java/io/github/dsheirer/buffer/{airspy/AirspyBufferIteratorVector64Bits.java => sample/SampleBufferIteratorVector64Bits.java} (94%) rename src/main/java/io/github/dsheirer/buffer/{airspy/AirspyInterleavedBufferIteratorScalar.java => sample/SampleInterleavedBufferIteratorScalar.java} (88%) rename src/main/java/io/github/dsheirer/buffer/{airspy/AirspyInterleavedBufferIteratorVector128Bits.java => sample/SampleInterleavedBufferIteratorVector128Bits.java} (92%) rename src/main/java/io/github/dsheirer/buffer/{airspy/AirspyInterleavedBufferIteratorVector256Bits.java => sample/SampleInterleavedBufferIteratorVector256Bits.java} (92%) rename src/main/java/io/github/dsheirer/buffer/{airspy/AirspyInterleavedBufferIteratorVector512Bits.java => sample/SampleInterleavedBufferIteratorVector512Bits.java} (92%) rename src/main/java/io/github/dsheirer/buffer/{airspy/AirspyInterleavedBufferIteratorVector64Bits.java => sample/SampleInterleavedBufferIteratorVector64Bits.java} (94%) rename src/main/java/io/github/dsheirer/buffer/{airspy/AirspyNativeBuffer.java => sample/SampleNativeBuffer.java} (78%) rename src/main/java/io/github/dsheirer/buffer/{airspy/AirspyNativeBufferFactory.java => sample/SampleNativeBufferFactory.java} (81%) rename src/main/java/io/github/dsheirer/buffer/{airspy => sample}/ScalarPackedSampleConverter.java (92%) create mode 100644 src/main/java/io/github/dsheirer/buffer/sample/ScalarUnpackedSampleConverter.java create mode 100644 src/main/java/io/github/dsheirer/buffer/sample/VectorUnpackedSampleConverter.java create mode 100644 src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrDeviceInformation.java create mode 100644 src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrSampleRate.java create mode 100644 src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrTuner.java create mode 100644 src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrTunerConfiguration.java create mode 100644 src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrTunerController.java create mode 100644 src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrTunerEditor.java rename src/main/java/io/github/dsheirer/vector/calibrate/{airspy/AirspySampleConverterCalibration.java => sample/PackedSampleConverterCalibration.java} (81%) rename src/main/java/io/github/dsheirer/vector/calibrate/{airspy/AirspyUnpackedInterleavedCalibration.java => sample/UnpackedInterleavedSampleConverterCalibration.java} (76%) rename src/main/java/io/github/dsheirer/vector/calibrate/{airspy/AirspyUnpackedCalibration.java => sample/UnpackedSampleConverterCalibration.java} (77%) create mode 100644 src/main/resources/52-hydrasdr.rules 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"