#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 <dennis.sheirer@gmail.com>
This commit is contained in:
Denny Sheirer
2026-10-31 06:15:02 -04:00
committed by GitHub
parent 85bfd21257
commit e65ff5a8dc
21 changed files with 465 additions and 2410 deletions

View File

@@ -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

View File

@@ -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' }
}
}
}

View File

@@ -1,119 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.buffer.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 <T> either ComplexSamples or InterleavedComplexSamples
*/
public abstract class HydraSdrBufferIterator<T> implements Iterator<T>
{
protected static final float SCALE_SIGNED_12_BIT_TO_FLOAT = 1.0f / 2048.0f;
protected static final int FRAGMENT_SIZE = 4096;
protected static final float[] COEFFICIENTS =
HilbertTransform.convertHalfBandToHilbert(HilbertTransform.HALF_BAND_FILTER_47_TAP);
public static final int I_OVERLAP = 11;
public static final int Q_OVERLAP = 23;
protected float[] mIBuffer = new float[FRAGMENT_SIZE + I_OVERLAP];
protected float[] mQBuffer = new float[FRAGMENT_SIZE + Q_OVERLAP];
protected short[] mSamples;
protected int mSamplesPointer = 0;
protected float mAverageDc;
private long mTimestamp;
private float mSamplesPerMillisecond;
/**
* Constructs an instance
* @param samples from the 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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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<ComplexSamples>
{
/**
* 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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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<ComplexSamples>
{
private static final VectorSpecies<Float> 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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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<ComplexSamples>
{
private static final VectorSpecies<Float> 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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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<ComplexSamples>
{
private static final VectorSpecies<Float> 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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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<ComplexSamples>
{
private static final VectorSpecies<Float> 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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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<InterleavedComplexSamples>
{
/**
* 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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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<InterleavedComplexSamples>
{
private static final VectorSpecies<Float> 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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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<InterleavedComplexSamples>
{
private static final VectorSpecies<Float> 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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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<InterleavedComplexSamples>
{
private static final VectorSpecies<Float> 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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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<InterleavedComplexSamples>
{
private static final VectorSpecies<Float> 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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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();
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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();
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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();
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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<Short> VECTOR_SPECIES = ShortVector.SPECIES_PREFERRED;
/**
* Manages DC calculations and processing interval
*/
private DcCorrectionManager mDcCalculationManager = new DcCorrectionManager();
@Override
public short[] convert(ByteBuffer buffer)
{
boolean shouldCalculateDc = mDcCalculationManager.shouldCalculateDc();
short sample;
short[] samples;
byte b1, b2;
samples = new short[buffer.capacity() / 2];
int bytesOffset;
int rawPointer = 0;
ShortVector vector;
int samplesOffset = 0;
short[] bytes1 = new short[VECTOR_SPECIES.length()];
short[] bytes2 = new short[VECTOR_SPECIES.length()];
if(shouldCalculateDc)
{
long dcAccumulator = 0;
for(; samplesOffset < VECTOR_SPECIES.loopBound(samples.length); samplesOffset += VECTOR_SPECIES.length())
{
bytesOffset = 0;
while(bytesOffset < VECTOR_SPECIES.length())
{
bytes1[bytesOffset] = (short)(buffer.get(rawPointer++) & 0xFF);
bytes2[bytesOffset++] = (short)(buffer.get(rawPointer++) & 0x0F);
}
vector = ShortVector.fromArray(VECTOR_SPECIES, bytes1, 0)
.or(ShortVector.fromArray(VECTOR_SPECIES, bytes2, 0).lanewise(VectorOperators.LSHL, 8));
dcAccumulator += vector.reduceLanes(VectorOperators.ADD);
vector.intoArray(samples, samplesOffset);
}
for(; samplesOffset < samples.length; samplesOffset++)
{
b1 = buffer.get(rawPointer++);
b2 = buffer.get(rawPointer++);
sample = (short)(((b2 & 0x0F) << 8) | (b1 & 0xFF));
samples[samplesOffset] = sample;
dcAccumulator += sample;
}
float averageDcNow = ((float)dcAccumulator / (float)samples.length) - 2048.0f;
averageDcNow *= 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();
}
}

View File

@@ -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();
}

View File

@@ -22,7 +22,6 @@ import io.github.dsheirer.preference.source.ChannelizerType;
import io.github.dsheirer.source.tuner.ITunerErrorListener;
import io.github.dsheirer.source.tuner.Tuner;
import io.github.dsheirer.source.tuner.TunerClass;
import io.github.dsheirer.source.tuner.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

View File

@@ -722,16 +722,15 @@ public class HydraSdrTunerEditor extends TunerEditor<HydraSdrTuner, HydraSdrTune
sb.append("<br>");
sb.append("<b>Firmware: </b>");
String[] firmware = info.getVersion().split(" ");
sb.append(firmware.length > 1 ? firmware[0] : info.getVersion());
sb.append(info.getVersion());
sb.append("<br>");
sb.append("<b>Part: </b>");
sb.append(info.getPartNumber());
sb.append("<br>");
sb.append("<b>Board ID: </b>");
sb.append(info.getBoardID().getLabel());
sb.append("<b>Board: </b>");
sb.append(info.getBoardName());
sb.append("<br>");
return sb.toString();