From e65ff5a8dc62549f747c0ee689bcfeb8a47a3e83 Mon Sep 17 00:00:00 2001 From: Denny Sheirer Date: Sat, 31 Oct 2026 06:15:02 -0400 Subject: [PATCH] #2540 HydraSDR tuner uses jHydraSDR library and removes legacy java driver code. (#2541) - Uses libhydrasdr HIGH_DEFINITION decimation mode by default. - Tuner controller extends TunerController since libhydrasdr owns the USB device, transfers and real to complex sample conversion. - Tuners are still discovered by USB bus and port. The controller reads the USB serial string and opens the device by serial number. - Samples are scaled by 2 to preserve existing levels (-6 dB otherwise). - Streaming is stopped for sample rate changes because libhydrasdr replaces the sample converter in use by its streaming thread. - Buffer sample count follows the delivered power-of-2 buffer size and adds the 1.25 MHz sample rate. Co-authored-by: denny --- build.gradle | 1 + settings.gradle | 8 + .../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 --- .../hydrasdr/IHydraSdrSampleConverter.java | 40 - .../hydrasdr/ScalarPackedSampleConverter.java | 94 -- .../ScalarUnpackedSampleConverter.java | 83 -- .../VectorUnpackedSampleConverter.java | 129 --- .../hydrasdr/HydraSdrDeviceInformation.java | 150 +-- .../source/tuner/hydrasdr/HydraSdrTuner.java | 3 +- .../hydrasdr/HydraSdrTunerController.java | 1028 +++++++---------- .../tuner/hydrasdr/HydraSdrTunerEditor.java | 7 +- 21 files changed, 465 insertions(+), 2410 deletions(-) delete mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIterator.java delete mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIteratorScalar.java delete mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIteratorVector128Bits.java delete mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIteratorVector256Bits.java delete mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIteratorVector512Bits.java delete mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIteratorVector64Bits.java delete mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrInterleavedBufferIteratorScalar.java delete mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrInterleavedBufferIteratorVector128Bits.java delete mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrInterleavedBufferIteratorVector256Bits.java delete mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrInterleavedBufferIteratorVector512Bits.java delete mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrInterleavedBufferIteratorVector64Bits.java delete mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/IHydraSdrSampleConverter.java delete mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/ScalarPackedSampleConverter.java delete mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/ScalarUnpackedSampleConverter.java delete mode 100644 src/main/java/io/github/dsheirer/buffer/hydrasdr/VectorUnpackedSampleConverter.java diff --git a/build.gradle b/build.gradle index 33b3045d..81021e5c 100644 --- a/build.gradle +++ b/build.gradle @@ -99,6 +99,7 @@ dependencies { implementation 'com.mpatric:mp3agic:0.9.1' implementation 'commons-io:commons-io:2.22.0' implementation 'eu.hansolo:charts:1.0.5' + implementation 'io.github.dsheirer:jhydrasdr:1.1.3' //HydraSDR tuners - GitHub release, see settings.gradle implementation 'io.github.dsheirer:radio-reference-api:18.0.0' implementation 'io.github.dsheirer:usb4java-native-libraries:1.3.3' //OSX & Windows aarch64 native libs implementation 'io.github.dsheirer:usb4java:1.3.5' //Fork with Loader support for OSX Multi-Version diff --git a/settings.gradle b/settings.gradle index ad996325..e691aca6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,5 +8,13 @@ dependencyResolutionManagement { repositories { mavenCentral() maven { url 'https://jitpack.io' } + + //jHydraSDR is published as a GitHub release, with the native libraries for all platforms in the jar + ivy { + url = 'https://github.com/DSheirer/jHydraSDR/releases/download' + patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' } + metadataSources { artifact() } + content { includeModule 'io.github.dsheirer', 'jhydrasdr' } + } } } diff --git a/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIterator.java b/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIterator.java deleted file mode 100644 index 8bba8e5a..00000000 --- a/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIterator.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2025 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.buffer.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 deleted file mode 100644 index 2594f107..00000000 --- a/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIteratorScalar.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -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 deleted file mode 100644 index fe2fe1c7..00000000 --- a/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIteratorVector128Bits.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -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 deleted file mode 100644 index bf828911..00000000 --- a/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIteratorVector256Bits.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -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 deleted file mode 100644 index bfd7e7a1..00000000 --- a/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIteratorVector512Bits.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -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 deleted file mode 100644 index 75454525..00000000 --- a/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrBufferIteratorVector64Bits.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -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 deleted file mode 100644 index c484d1e4..00000000 --- a/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrInterleavedBufferIteratorScalar.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -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 deleted file mode 100644 index 2033a6b7..00000000 --- a/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrInterleavedBufferIteratorVector128Bits.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -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 deleted file mode 100644 index 742c361b..00000000 --- a/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrInterleavedBufferIteratorVector256Bits.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -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 deleted file mode 100644 index 39dc36dc..00000000 --- a/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrInterleavedBufferIteratorVector512Bits.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -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 deleted file mode 100644 index 048465b1..00000000 --- a/src/main/java/io/github/dsheirer/buffer/hydrasdr/HydraSdrInterleavedBufferIteratorVector64Bits.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -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/hydrasdr/IHydraSdrSampleConverter.java b/src/main/java/io/github/dsheirer/buffer/hydrasdr/IHydraSdrSampleConverter.java deleted file mode 100644 index 771f559b..00000000 --- a/src/main/java/io/github/dsheirer/buffer/hydrasdr/IHydraSdrSampleConverter.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.buffer.hydrasdr; - -import java.nio.ByteBuffer; - -/** - * Interface to convert HydraSDR samples from a byte buffer to an array of shorts. - */ -public interface IHydraSdrSampleConverter -{ - /** - * 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); - - /** - * Current DC average of the samples that have been processed thus far. - */ - float getAverageDc(); -} diff --git a/src/main/java/io/github/dsheirer/buffer/hydrasdr/ScalarPackedSampleConverter.java b/src/main/java/io/github/dsheirer/buffer/hydrasdr/ScalarPackedSampleConverter.java deleted file mode 100644 index 04d40d0b..00000000 --- a/src/main/java/io/github/dsheirer/buffer/hydrasdr/ScalarPackedSampleConverter.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -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/hydrasdr/ScalarUnpackedSampleConverter.java b/src/main/java/io/github/dsheirer/buffer/hydrasdr/ScalarUnpackedSampleConverter.java deleted file mode 100644 index 6c0fc1fa..00000000 --- a/src/main/java/io/github/dsheirer/buffer/hydrasdr/ScalarUnpackedSampleConverter.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.buffer.hydrasdr; - -import io.github.dsheirer.buffer.DcCorrectionManager; -import java.nio.ByteBuffer; - -/** - * Scalar implementation of HydraSDR sample converter for un-packed samples. - */ -public class ScalarUnpackedSampleConverter 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; - - 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 *= HydraSdrBufferIterator.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/hydrasdr/VectorUnpackedSampleConverter.java b/src/main/java/io/github/dsheirer/buffer/hydrasdr/VectorUnpackedSampleConverter.java deleted file mode 100644 index c0a6cc34..00000000 --- a/src/main/java/io/github/dsheirer/buffer/hydrasdr/VectorUnpackedSampleConverter.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.buffer.hydrasdr; - -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; - -/** - * Vector implementation of HydraSDR sample converter for un-packed samples. - */ -public class VectorUnpackedSampleConverter implements IHydraSdrSampleConverter -{ - 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 *= HydraSdrBufferIterator.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/hydrasdr/HydraSdrDeviceInformation.java b/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrDeviceInformation.java index 21fe36fe..3041fc70 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrDeviceInformation.java +++ b/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrDeviceInformation.java @@ -1,156 +1,60 @@ package io.github.dsheirer.source.tuner.hydrasdr; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Arrays; +import io.github.dsheirer.hydrasdr.DeviceInfo; +/** + * HydraSDR device information, formatted for display and for identifying the tuner. + */ 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"; + private final DeviceInfo mDeviceInfo; /** - * HydraSDR Device Information - provides parsing and access to device - * information components. + * Constructs an instance + * @param deviceInfo from the device */ - public HydraSdrDeviceInformation() + public HydraSdrDeviceInformation(DeviceInfo deviceInfo) { + mDeviceInfo = deviceInfo; } - public void setBoardID( int boardID ) + public String getBoardName() { - 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 ) ); - } + return mDeviceInfo.boardName(); } 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 ); + return mDeviceInfo.firmwareVersion().replace("HydraSDR ", ""); } + /** + * Serial number, formatted as the identifier that tuner configurations are saved under. + */ public String getSerialNumber() { - return mSerialNumber; + long serial = mDeviceInfo.serialNumber(); + return String.format("%08X-%08X", serial >>> 32, serial & 0xFFFFFFFFL); } public String getPartNumber() { - return mPartNumber; + return String.format("%016X", mDeviceInfo.partId()); } 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() ); + sb.append("HydraSDR Device Information\n"); + sb.append("Board: "); + sb.append(getBoardName()); + sb.append("\nPart Number: "); + sb.append(getPartNumber()); + sb.append("\nSerial Number: "); + sb.append(getSerialNumber()); + sb.append("\nFirmware: "); + sb.append(getVersion()); return sb.toString(); } diff --git a/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrTuner.java b/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrTuner.java index 134dbd6b..205da2f9 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrTuner.java +++ b/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrTuner.java @@ -22,7 +22,6 @@ import io.github.dsheirer.preference.source.ChannelizerType; import io.github.dsheirer.source.tuner.ITunerErrorListener; import io.github.dsheirer.source.tuner.Tuner; import io.github.dsheirer.source.tuner.TunerClass; -import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTunerController.BoardID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -71,7 +70,7 @@ public class HydraSdrTuner extends Tuner mLog.error("error getting serial number", e); } - return BoardID.HYDRASDR_RFONE.getLabel(); + return "HydraSDR RFOne"; } @Override 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 index 3b72b25d..f359de8e 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrTunerController.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2025 Dennis Sheirer + * Copyright (C) 2014-2026 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,31 +19,42 @@ package io.github.dsheirer.source.tuner.hydrasdr; -import io.github.dsheirer.buffer.INativeBufferFactory; -import io.github.dsheirer.buffer.sample.SampleNativeBufferFactory; +import io.github.dsheirer.buffer.FloatNativeBuffer; +import io.github.dsheirer.buffer.INativeBuffer; +import io.github.dsheirer.hydrasdr.DecimationMode; +import io.github.dsheirer.hydrasdr.GainType; +import io.github.dsheirer.hydrasdr.HydraSdr; +import io.github.dsheirer.hydrasdr.HydraSdrDevice; +import io.github.dsheirer.hydrasdr.HydraSdrException; +import io.github.dsheirer.hydrasdr.SampleBuffer; +import io.github.dsheirer.hydrasdr.SampleType; +import io.github.dsheirer.sample.Listener; import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.tuner.ITunerErrorListener; +import io.github.dsheirer.source.tuner.TunerController; import io.github.dsheirer.source.tuner.TunerType; import io.github.dsheirer.source.tuner.configuration.TunerConfiguration; -import io.github.dsheirer.source.tuner.usb.USBTunerController; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; +import io.github.dsheirer.source.tuner.manager.TunerManager; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; -import org.apache.commons.io.EndianUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.usb4java.Context; +import org.usb4java.Device; +import org.usb4java.DeviceDescriptor; +import org.usb4java.DeviceHandle; +import org.usb4java.DeviceList; import org.usb4java.LibUsb; -import org.usb4java.LibUsbException; - -import javax.usb.UsbException; /** - * HydraSDR tuner controller + * HydraSDR tuner controller, implemented with the jHydraSDR library (libhydrasdr). + * + * The tuner is discovered with LibUsb at a USB bus and port, like all other USB tuners, however libhydrasdr owns the + * USB device, its transfers, and the conversion of real samples to complex samples. */ -public class HydraSdrTunerController extends USBTunerController +public class HydraSdrTunerController extends TunerController { private final static Logger mLog = LoggerFactory.getLogger(HydraSdrTunerController.class); public static final Gain LINEARITY_GAIN_DEFAULT = Gain.LINEARITY_14; @@ -63,16 +74,25 @@ public class HydraSdrTunerController extends USBTunerController public static final long MAXIMUM_TUNABLE_FREQUENCY_HZ = 1800000000l; public static final long FREQUENCY_DEFAULT = 101100000; public static final double USABLE_BANDWIDTH_PERCENT = 0.90; - private static final long USB_TIMEOUT_MS = 2000l; //milliseconds - private static final int USB_TRANSFER_BUFFER_SIZE = 262144; - private static final byte USB_REQUEST_IN = (byte) (LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE); - private static final byte USB_REQUEST_OUT = (byte) (LibUsb.ENDPOINT_OUT | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE); public static final DecimalFormat MHZ_FORMATTER = new DecimalFormat("#.00 MHz"); public static final HydraSdrSampleRate DEFAULT_SAMPLE_RATE = new HydraSdrSampleRate(0, 10000000, "10.00 MHz"); - private SampleNativeBufferFactory mNativeBufferFactory = new SampleNativeBufferFactory(); + //Lower rates from libhydrasdr don't divide into whole 25 kHz polyphase channels, and the lowest give none at all. + private static final long MINIMUM_SAMPLE_RATE = 1250000; + //Power-of-2 sample count of an undecimated buffer, used until buffers arrive from the device. libhydrasdr divides + //this by the software decimation factor, which is also a power of 2. + private static final int UNDECIMATED_BUFFER_SAMPLE_COUNT = 65536; + //Complex samples from libhydrasdr are half the amplitude (-6 dB) of the analytic signal for a real input. Restoring + //this keeps spectrum levels and squelch thresholds consistent with the other tuners. + private static final float SAMPLE_SCALE = 2.0f; + private final int mBus; + private final String mPortAddress; + private HydraSdrDevice mDevice; private HydraSdrDeviceInformation mDeviceInfo; private List mSampleRates = new ArrayList<>(); private int mSampleRate = 0; + private volatile float mSamplesPerMillisecond = 0.0f; + private volatile int mBufferSampleCount = UNDECIMATED_BUFFER_SAMPLE_COUNT; + private boolean mStreaming = false; /** * Constructs an instance @@ -82,7 +102,13 @@ public class HydraSdrTunerController extends USBTunerController */ public HydraSdrTunerController(int bus, String portAddress, ITunerErrorListener tunerErrorListener) { - super(bus, portAddress, MINIMUM_TUNABLE_FREQUENCY_HZ, MAXIMUM_TUNABLE_FREQUENCY_HZ, 0, USABLE_BANDWIDTH_PERCENT, tunerErrorListener); + super(tunerErrorListener); + mBus = bus; + mPortAddress = portAddress; + setMinimumFrequency(MINIMUM_TUNABLE_FREQUENCY_HZ); + setMaximumFrequency(MAXIMUM_TUNABLE_FREQUENCY_HZ); + setMiddleUnusableHalfBandwidth(0); + setUsableBandwidthPercentage(USABLE_BANDWIDTH_PERCENT); } @Override @@ -91,77 +117,334 @@ public class HydraSdrTunerController extends USBTunerController 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; + return mBufferSampleCount; } @Override - protected void deviceStart() throws SourceException + public void start() throws SourceException { - try - { - setSamplePacking(false); - } - catch(LibUsbException | UsbException | UnsupportedOperationException e) - { - mLog.info("Sample packing is not supported by HydraSDR firmware"); - } + long serialNumber = readSerialNumber(); + + getLock().lock(); try { - setReceiverMode(true); - } - catch(Exception e) - { - mLog.error("Couldn't enable HydraSDR receiver mode", e); - } + mDevice = HydraSdr.open(serialNumber); + mDevice.setSampleType(SampleType.FLOAT32_IQ); + mDevice.setDecimationMode(DecimationMode.HIGH_DEFINITION); + mDeviceInfo = new HydraSdrDeviceInformation(mDevice.deviceInfo()); + mSampleRates.clear(); - setFrequency(FREQUENCY_DEFAULT); + for(long rate : mDevice.sampleRates()) + { + if(rate >= MINIMUM_SAMPLE_RATE) + { + mSampleRates.add(new HydraSdrSampleRate(mSampleRates.size(), (int)rate, formatSampleRate((int)rate))); + } + } - try - { - determineAvailableSampleRates(); - } - catch(LibUsbException | UsbException e) - { - mLog.error("Error identifying available samples rates", e); - } + if(mSampleRates.isEmpty()) + { + mSampleRates.add(DEFAULT_SAMPLE_RATE); + } - try - { + setFrequency(FREQUENCY_DEFAULT); setSampleRate(mSampleRates.get(0)); } - catch(IllegalArgumentException | LibUsbException | UsbException e) + catch(HydraSdrException | SourceException | LinkageError e) { - mLog.error("Setting sample rate is not supported by firmware", e); + closeDevice(mDevice); + mDevice = null; + throw new SourceException("Can't start HydraSDR tuner - " + e.getMessage(), e); + } + finally + { + getLock().unlock(); } } @Override - protected void deviceStop() + public void stop() + { + HydraSdrDevice device; + + getLock().lock(); + + try + { + device = mDevice; + mDevice = null; + mStreaming = false; + } + finally + { + getLock().unlock(); + } + + mNativeBufferBroadcaster.clear(); + closeDevice(device); + } + + /** + * Closes the device, which also stops streaming and turns off the bias-T. + * @param device to close, or null + */ + private static void closeDevice(HydraSdrDevice device) + { + if(device != null) + { + try + { + device.close(); + } + catch(HydraSdrException | RuntimeException e) + { + mLog.error("Error closing HydraSDR device", e); + } + } + } + + /** + * Reads the serial number of the device at this controller's USB bus and port, since libhydrasdr opens a specific + * device by serial number. The device is closed again before returning so that libhydrasdr can open it. + * @return serial number + * @throws SourceException if the device isn't found or can't be accessed. + */ + private long readSerialNumber() throws SourceException + { + Context context = new Context(); + int status = LibUsb.init(context); + + if(status != LibUsb.SUCCESS) + { + throw new SourceException("Can't initialize libusb library - " + LibUsb.errorName(status)); + } + + try + { + DeviceList deviceList = new DeviceList(); + int count = LibUsb.getDeviceList(context, deviceList); + + if(count < 0) + { + throw new SourceException("Can't get the USB device list - " + LibUsb.errorName(count)); + } + + try + { + for(Device device : deviceList) + { + if(LibUsb.getBusNumber(device) == mBus && LibUsb.getPortNumber(device) > 0 && + mPortAddress != null && mPortAddress.equals(TunerManager.getPortAddress(device))) + { + return readSerialNumber(device); + } + } + } + finally + { + LibUsb.freeDeviceList(deviceList, true); + } + } + finally + { + LibUsb.exit(context); + } + + throw new SourceException("Couldn't find USB device at bus [" + mBus + "] port [" + mPortAddress + "]"); + } + + /** + * Reads the serial number from the device's USB serial string, formatted as: HYDRASDR SN:0123456789ABCDEF + * @param device to read + * @return serial number + * @throws SourceException if the device can't be opened or the serial string isn't valid. + */ + private static long readSerialNumber(Device device) throws SourceException + { + DeviceDescriptor descriptor = new DeviceDescriptor(); + int status = LibUsb.getDeviceDescriptor(device, descriptor); + + if(status != LibUsb.SUCCESS) + { + throw new SourceException("Can't obtain tuner's device descriptor - " + LibUsb.errorName(status)); + } + + DeviceHandle handle = new DeviceHandle(); + status = LibUsb.open(device, handle); + + if(status == LibUsb.ERROR_ACCESS) + { + mLog.error("Access to USB tuner denied - (windows) reinstall zadig driver or (linux) blacklist driver and/or check udev rules"); + throw new SourceException("access denied - if using linux, blacklist the default driver and/or install udev rules"); + } + else if(status != LibUsb.SUCCESS) + { + mLog.error("Can't open USB tuner - check driver or Linux udev rules"); + throw new SourceException("Can't open USB tuner - reinstall driver? - " + LibUsb.errorName(status)); + } + + String serial; + + try + { + serial = LibUsb.getStringDescriptor(handle, descriptor.iSerialNumber()); + } + finally + { + LibUsb.close(handle); + } + + try + { + return Long.parseUnsignedLong(serial.substring(serial.lastIndexOf(':') + 1), 16); + } + catch(NullPointerException | NumberFormatException e) + { + throw new SourceException("Can't read HydraSDR serial number from USB serial string [" + serial + "]", e); + } + } + + /** + * Operation that requires the device. + */ + private interface DeviceOperation + { + void execute(HydraSdrDevice device) throws HydraSdrException; + } + + /** + * Executes the operation under the controller lock so that the device can't be closed while it is in use. The + * operation is ignored when the device isn't open, which allows for calls that arrive during tuner shutdown. + * @param operation to execute + * @throws SourceException if the operation fails + */ + private void execute(DeviceOperation operation) throws SourceException + { + getLock().lock(); + + try + { + if(mDevice != null) + { + operation.execute(mDevice); + } + } + catch(HydraSdrException e) + { + throw new SourceException(e.getMessage(), e); + } + finally + { + getLock().unlock(); + } + } + + /** + * Receives converted samples on the libhydrasdr thread and dispatches them to the buffer listeners. Exceptions + * are contained here since an exception reaching the library would stop the sample stream. + * @param buffer from the library, which is only valid until this method returns. + * @return true to continue streaming + */ + private boolean receive(SampleBuffer buffer) { try { - setReceiverMode(false); + mBufferSampleCount = buffer.sampleCount(); + float[] samples = buffer.copyFloats(); + + for(int x = 0; x < samples.length; x++) + { + samples[x] *= SAMPLE_SCALE; + } + + broadcast(new FloatNativeBuffer(samples, System.currentTimeMillis(), mSamplesPerMillisecond)); } - catch(Exception e) + catch(RuntimeException e) { - mLog.error("Couldn't enable HydraSDR receiver mode", e); + mLog.error("Error dispatching HydraSDR sample buffer", e); + } + + return true; + } + + /** + * Starts the sample stream if it's not already started. + */ + private void startStreaming() throws SourceException + { + if(!mStreaming) + { + execute(device -> device.startRx(this::receive)); + mStreaming = mDevice != null; + } + } + + /** + * Stops the sample stream if it's started. + */ + private void stopStreaming() throws SourceException + { + if(mStreaming) + { + mStreaming = false; + execute(HydraSdrDevice::stopRx); + } + } + + /** + * Adds the IQ buffer listener and automatically starts streaming, if not already started. + */ + @Override + public void addBufferListener(Listener listener) + { + getLock().lock(); + + try + { + if(mDevice != null) + { + super.addBufferListener(listener); + startStreaming(); + } + } + catch(SourceException se) + { + mLog.error("Error starting streaming on HydraSDR tuner", se); + } + finally + { + getLock().unlock(); + } + } + + /** + * Removes the IQ buffer listener and stops streaming if there are no more listeners. + */ + @Override + public void removeBufferListener(Listener listener) + { + getLock().lock(); + + try + { + super.removeBufferListener(listener); + + if(!hasBufferListeners()) + { + stopStreaming(); + } + } + catch(SourceException se) + { + mLog.error("Error stopping streaming on HydraSDR tuner", se); + } + finally + { + getLock().unlock(); } } @@ -173,29 +456,8 @@ public class HydraSdrTunerController extends USBTunerController 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); - } + HydraSdrSampleRate rate = getSampleRate(config.getSampleRate()); + setSampleRate(rate != null ? rate : mSampleRates.isEmpty() ? DEFAULT_SAMPLE_RATE : mSampleRates.get(0)); try { @@ -231,24 +493,11 @@ public class HydraSdrTunerController extends USBTunerController } @Override - public synchronized void setTunedFrequency(long frequency) throws SourceException + public void setTunedFrequency(long frequency) throws SourceException { if(MINIMUM_TUNABLE_FREQUENCY_HZ <= frequency && frequency <= MAXIMUM_TUNABLE_FREQUENCY_HZ) { - ByteBuffer buffer = ByteBuffer.allocateDirect(4); - buffer.order(ByteOrder.LITTLE_ENDIAN); - buffer.putInt((int) frequency); - buffer.rewind(); - - try - { - write(Command.SET_FREQUENCY, 0, 0, buffer); - } - catch(UsbException e) - { - mLog.error("error setting frequency [" + frequency + "]", e); - throw new SourceException("error setting frequency [" + frequency + "]", e); - } + execute(device -> device.setFrequency(frequency)); } else { @@ -263,52 +512,71 @@ public class HydraSdrTunerController extends USBTunerController } /** - * Sets the sample rate to the rate specified by the index value in the available sample rates map + * Sets the sample rate. The sample stream is stopped for the change, because libhydrasdr replaces the sample + * converter that its streaming thread is using. * - * @param rate to a sample rate in the available samples rates map. - * @throws IllegalArgumentException if index is not a valid rate index - * @throws LibUsbException if there was a read error or if this operation is not supported by the current firmware - * @throws UsbException if there was a USB error + * @param rate to set + * @throws SourceException if the rate can't be set */ - public void setSampleRate(HydraSdrSampleRate rate) throws LibUsbException, UsbException, SourceException + public void setSampleRate(HydraSdrSampleRate rate) throws SourceException { - getNativeBufferFactory().setSamplesPerMillisecond((float)rate.getRate() / 1000.0f); - if(rate.getRate() != mSampleRate) + getLock().lock(); + + try { - int result = readByte(Command.SET_SAMPLE_RATE, 0, rate.getIndex(), true); + mSamplesPerMillisecond = (float)rate.getRate() / 1000.0f; - if(result != 1) + if(rate.getRate() != mSampleRate) { - throw new UsbException("Error setting sample rate [" + rate + "] rate - return value [" + result + "]"); - } - else - { - mSampleRate = rate.getRate(); - mFrequencyController.setSampleRate(mSampleRate); + boolean streaming = mStreaming; + stopStreaming(); - // Update the Usable Bandwidth Percentage, as the usable range varies with the sample rate. - switch(mSampleRate) + try { - default: - case 10000000: - super.setUsableBandwidthPercentage(0.90); - break; - case 6000000: - super.setUsableBandwidthPercentage(0.83); - break; - case 3000000: - super.setUsableBandwidthPercentage(0.66); - break; - case 2500000: - super.setUsableBandwidthPercentage(0.60); - break; + execute(device -> + { + device.setSampleRate(rate.getRate()); + mBufferSampleCount = UNDECIMATED_BUFFER_SAMPLE_COUNT / + (int)device.deviceInfo().current().decimationFactor(); + }); + 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.90); + break; + } + } + finally + { + if(streaming) + { + startStreaming(); + } } } } + finally + { + getLock().unlock(); + } } /** - * Returns a list of sample rates supported by the firmware version + * Returns a list of sample rates supported by the device */ public List getSampleRates() { @@ -342,62 +610,29 @@ public class HydraSdrTunerController extends USBTunerController return null; } - /** - * Enables/Disables sample packing to allow two 12-bit samples to be packed into 3 bytes (enabled) or 4 bytes (disabled). - * - * @param enabled true or false - * @throws UsbException if sample packing is not supported by the current device firmware or if there were usb - * communication issues - */ - public void setSamplePacking(boolean enabled) throws LibUsbException, UsbException - { - int result = readByte(Command.SET_PACKING, 0, (enabled ? 1 : 0), true); - - if(result != 1) - { - throw new UsbException("Couldnt set sample packing enabled: " + enabled); - } - - /* If we didn't throw an exception above, then update the sample adapter - * to process samples accordingly */ - mNativeBufferFactory.setSamplePacking(enabled); - } - /** * Enables/disables the mixer automatic gain setting * - * @param enabled - * @throws LibUsbException on unsuccessful read operation - * @throws UsbException on USB error + * @param enabled true to enable + * @throws SourceException on error */ - public synchronized void setMixerAGC(boolean enabled) throws LibUsbException, UsbException + public void setMixerAGC(boolean enabled) throws SourceException { - int result = readByte(Command.SET_MIXER_AGC, 0, (enabled ? 1 : 0), true); - - if(result != LibUsb.SUCCESS) - { - throw new UsbException("Couldnt set mixer AGC enabled: " + enabled); - } + execute(device -> device.setGain(GainType.MIXER_AGC, enabled ? 1 : 0)); } /** * Enables/disables the low noise amplifier automatic gain setting * - * @param enabled - * @throws LibUsbException on unsuccessful read operation - * @throws UsbException on USB error + * @param enabled true to enable + * @throws SourceException on error */ - public synchronized void setLNAAGC(boolean enabled) throws LibUsbException, UsbException + public void setLNAAGC(boolean enabled) throws SourceException { - int result = readByte(Command.SET_LNA_AGC, 0, (enabled ? 1 : 0), true); - - if(result != LibUsb.SUCCESS) - { - throw new UsbException("Couldn't set LNA AGC enabled: " + enabled); - } + execute(device -> device.setGain(GainType.LNA_AGC, enabled ? 1 : 0)); } - public void setGain(Gain gain) throws UsbException + public void setGain(Gain gain) throws SourceException { if(gain != Gain.CUSTOM) { @@ -413,20 +648,14 @@ public class HydraSdrTunerController extends USBTunerController * Sets LNA gain * * @param gain - value within range of LNA_GAIN_MIN to LNA_GAIN_MAX - * @throws LibUsbException on error in java USB wrapper - * @throws UsbException on error in USB transfer + * @throws SourceException on error * @throws IllegalArgumentException if gain value is invalid */ - public synchronized void setLNAGain(int gain) throws LibUsbException, UsbException, IllegalArgumentException + public void setLNAGain(int gain) throws SourceException, IllegalArgumentException { if(LNA_GAIN_MIN <= gain && gain <= LNA_GAIN_MAX) { - int result = readByte(Command.SET_LNA_GAIN, 0, gain, true); - - if(result != LibUsb.SUCCESS) - { - throw new UsbException("Couldnt set LNA gain to: " + gain); - } + execute(device -> device.setGain(GainType.LNA, gain)); } else { @@ -439,20 +668,14 @@ public class HydraSdrTunerController extends USBTunerController * Sets Mixer gain * * @param gain - value within range of MIXER_GAIN_MIN to MIXER_GAIN_MAX - * @throws LibUsbException on error in java USB wrapper - * @throws UsbException on error in USB transfer + * @throws SourceException on error * @throws IllegalArgumentException if gain value is invalid */ - public void setMixerGain(int gain) throws LibUsbException, UsbException, IllegalArgumentException + public void setMixerGain(int gain) throws SourceException, IllegalArgumentException { if(MIXER_GAIN_MIN <= gain && gain <= MIXER_GAIN_MAX) { - int result = readByte(Command.SET_MIXER_GAIN, 0, gain, true); - - if(result != LibUsb.SUCCESS) - { - throw new UsbException("Couldnt set mixer gain to: " + gain); - } + execute(device -> device.setGain(GainType.MIXER, gain)); } else { @@ -464,21 +687,15 @@ public class HydraSdrTunerController extends USBTunerController /** * Sets IF (VGA) gain * - * @param gain - value within range of VGA_GAIN_MIN to VGA_GAIN_MAX - * @throws LibUsbException on error in java USB wrapper - * @throws UsbException on error in USB transfer + * @param gain - value within range of IF_GAIN_MIN to IF_GAIN_MAX + * @throws SourceException on error * @throws IllegalArgumentException if gain value is invalid */ - public void setIFGain(int gain) throws LibUsbException, UsbException, IllegalArgumentException + public void setIFGain(int gain) throws SourceException, IllegalArgumentException { if(IF_GAIN_MIN <= gain && gain <= IF_GAIN_MAX) { - int result = readByte(Command.SET_VGA_GAIN, 0, gain, true); - - if(result != LibUsb.SUCCESS) - { - throw new UsbException("Couldnt set VGA gain to: " + gain); - } + execute(device -> device.setGain(GainType.VGA, gain)); } else { @@ -487,64 +704,15 @@ public class HydraSdrTunerController extends USBTunerController } } - public void setReceiverMode(boolean enabled) throws LibUsbException, UsbException - { - //Empty buffer to throw away - ByteBuffer buffer = ByteBuffer.allocateDirect(0); - write(Command.RECEIVER_MODE, (enabled ? 1 : 0), 0, buffer); - } - /** * 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 + * @throws SourceException on error */ - public synchronized void setBiasT(boolean enabled) throws LibUsbException, UsbException + public void setBiasT(boolean enabled) throws SourceException { - 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); - } + execute(device -> device.setBiasTee(enabled)); } /** @@ -556,164 +724,13 @@ public class HydraSdrTunerController extends USBTunerController } /** - * Device information + * Device information, available once the tuner has been started. */ 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, @@ -856,199 +873,4 @@ public class HydraSdrTunerController extends USBTunerController 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 index cbde8c5c..fa1cd2b1 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrTunerEditor.java +++ b/src/main/java/io/github/dsheirer/source/tuner/hydrasdr/HydraSdrTunerEditor.java @@ -722,16 +722,15 @@ public class HydraSdrTunerEditor extends TunerEditor"); sb.append("Firmware: "); - String[] firmware = info.getVersion().split(" "); - sb.append(firmware.length > 1 ? firmware[0] : info.getVersion()); + sb.append(info.getVersion()); sb.append("
"); sb.append("Part: "); sb.append(info.getPartNumber()); sb.append("
"); - sb.append("Board ID: "); - sb.append(info.getBoardID().getLabel()); + sb.append("Board: "); + sb.append(info.getBoardName()); sb.append("
"); return sb.toString();