diff --git a/build.gradle b/build.gradle index 3a88718d..836a43c9 100644 --- a/build.gradle +++ b/build.gradle @@ -125,6 +125,7 @@ dependencies { implementation 'commons-io:commons-io:2.22.0' implementation 'eu.hansolo:charts:1.0.5' implementation 'io.github.dsheirer:jbladerf:2.6.0' //bladeRF tuners - GitHub release, see settings.gradle + implementation 'io.github.dsheirer:jairspy:1.0.12' //Airspy tuners - GitHub release, see settings.gradle implementation 'io.github.dsheirer:jhydrasdr:1.1.3' //HydraSDR tuners - GitHub release, see settings.gradle implementation 'io.github.dsheirer:radio-reference-api:18.0.0' implementation 'io.github.dsheirer:usb4java-native-libraries:1.3.3' //OSX & Windows aarch64 native libs diff --git a/settings.gradle b/settings.gradle index f4985b54..0cc4eac9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -9,6 +9,14 @@ dependencyResolutionManagement { mavenCentral() maven { url 'https://jitpack.io' } + //jAirspy is published as a GitHub release, with the native libraries for all platforms in the jar + ivy { + url = 'https://github.com/DSheirer/jAirspy/releases/download' + patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' } + metadataSources { artifact() } + content { includeModule 'io.github.dsheirer', 'jairspy' } + } + //jHydraSDR is published as a GitHub release, with the native libraries for all platforms in the jar ivy { url = 'https://github.com/DSheirer/jHydraSDR/releases/download' diff --git a/src/main/java/io/github/dsheirer/buffer/sample/ISampleConverter.java b/src/main/java/io/github/dsheirer/buffer/sample/ISampleConverter.java deleted file mode 100644 index 906f9144..00000000 --- a/src/main/java/io/github/dsheirer/buffer/sample/ISampleConverter.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2025 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.buffer.sample; - -import java.nio.ByteBuffer; - -/** - * Interface to convert samples from a byte buffer to an array of shorts. - */ -public interface ISampleConverter -{ - /** - * Converts the byte samples contained in the byte buffer to their short-valued representation - * @param buffer of samples - * @return converted samples - */ - short[] convert(ByteBuffer buffer); - - /** - * Current DC average of the samples that have been processed thus far. - */ - float getAverageDc(); -} diff --git a/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIterator.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIterator.java deleted file mode 100644 index 9567aeab..00000000 --- a/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIterator.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2025 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.buffer.sample; - -import io.github.dsheirer.dsp.filter.hilbert.HilbertTransform; -import java.util.Iterator; - -/** - * Base complex samples iterator for raw sample buffers. Incorporates a Hilbert transform filter - * for converting the real sample array to complex samples. - * @param either ComplexSamples or InterleavedComplexSamples - */ -public abstract class SampleBufferIterator implements Iterator -{ - protected static final float SCALE_SIGNED_12_BIT_TO_FLOAT = 1.0f / 2048.0f; - protected static final int FRAGMENT_SIZE = 4096; - protected static final float[] COEFFICIENTS = - HilbertTransform.convertHalfBandToHilbert(HilbertTransform.HALF_BAND_FILTER_47_TAP); - - public static final int I_OVERLAP = 11; - public static final int Q_OVERLAP = 23; - - protected float[] mIBuffer = new float[FRAGMENT_SIZE + I_OVERLAP]; - protected float[] mQBuffer = new float[FRAGMENT_SIZE + Q_OVERLAP]; - protected short[] mSamples; - protected int mSamplesPointer = 0; - protected float mAverageDc; - private long mTimestamp; - private float mSamplesPerMillisecond; - - /** - * Constructs an instance - * @param samples from the tuner. - * @param residualI samples from previous buffer - * @param residualQ samples from previous buffer - * @param averageDc as measured - * @param timestamp of the buffer - * @param samplesPerMillisecond to calculate sub-buffer fragment timestamps - */ - public SampleBufferIterator(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp, - float samplesPerMillisecond) - { - if(residualI.length != I_OVERLAP || residualQ.length != Q_OVERLAP) - { - throw new IllegalArgumentException("Residual I length [" + residualI.length + - "] must be " + I_OVERLAP + " and Residual Q length [" + residualQ.length + - "] must be " + Q_OVERLAP); - } - - int requiredInterval = FRAGMENT_SIZE * 2; //requires 4 bytes (2 samples) per fragment - - if(samples.length % requiredInterval != 0) - { - throw new IllegalArgumentException("Samples short array length [" + mSamples.length + - "]must be an integer multiple of " + requiredInterval); - } - - mAverageDc = averageDc; - mSamples = samples; - mTimestamp = timestamp; - mSamplesPerMillisecond = samplesPerMillisecond; - - //Transfer and scale the residual I & Q samples from the previous buffer - for(int i = 0; i < residualI.length; i++) - { - mIBuffer[i] = scale(residualI[i], averageDc); - } - - for(int q = 0; q < residualQ.length; q++) - { - mQBuffer[q] = scale(residualQ[q], averageDc); - } - } - - /** - * Calculates the timestamp for a samples buffer fragment based on where it is extracted from relative to the - * native buffer starting timestamp. - * @param samplesPointer for the start of the fragment. Note: this value will be divided by 2 to account for I/Q sample pairs. - * @return timestamp adjusted to the fragment or sub-buffer start sample. - */ - protected long getFragmentTimestamp(int samplesPointer) - { - return mTimestamp + (long)(samplesPointer / 2 / mSamplesPerMillisecond); - } - - @Override - public boolean hasNext() - { - return mSamplesPointer < mSamples.length; - } - - /** - * Scales the short sample value to a 12-bit floating point and subtracts the average DC offset - * @param value to scale - * @param averageDc detected - * @return scaled and normalized sample - */ - public static float scale(short value, float averageDc) - { - return ((float)value - 2048) * SCALE_SIGNED_12_BIT_TO_FLOAT - averageDc; - } -} diff --git a/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorScalar.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorScalar.java deleted file mode 100644 index 383af553..00000000 --- a/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorScalar.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2025 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.buffer.sample; - -import io.github.dsheirer.sample.complex.ComplexSamples; -import java.util.Arrays; - -/** - * Scalar implementation for non-packed native buffers - */ -public class SampleBufferIteratorScalar extends SampleBufferIterator -{ - /** - * Constructs an instance - * - * @param samples from the device, either packed or unpacked. - * @param residualI samples from last buffer - * @param residualQ samples from last buffer - * @param averageDc measured - * @param timestamp of the buffer - * @param samplesPerMillisecond to calculate sub-buffer fragment timestamps - */ - public SampleBufferIteratorScalar(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp, float samplesPerMillisecond) - { - super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond); - } - - @Override - public ComplexSamples next() - { - if(mSamplesPointer >= mSamples.length) - { - throw new IllegalStateException("End of buffer exceeded"); - } - - long timestamp = getFragmentTimestamp(mSamplesPointer); - - int offset = mSamplesPointer; - - for(int x = 0; x < FRAGMENT_SIZE; x++) - { - mIBuffer[x + I_OVERLAP] = scale(mSamples[offset++], mAverageDc); - mQBuffer[x + Q_OVERLAP] = scale(mSamples[offset++], mAverageDc); - } - - mSamplesPointer = offset; - - //The i line is a simple delay line, so we'll just copy those as a new array here. - float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE); - float[] q = new float[FRAGMENT_SIZE]; - - float accumulator; - - for(int x = 0; x < FRAGMENT_SIZE; x++) - { - accumulator = 0; - - for(int tap = 0; tap < COEFFICIENTS.length; tap++) - { - accumulator += COEFFICIENTS[tap] * mQBuffer[x + tap]; - } - - //Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc. - if(x % 2 == 0) - { - q[x] = accumulator; - } - else - { - i[x] = -i[x]; - q[x] = -accumulator; - } - } - - //Copy residual end samples to beginning of buffers for the next iteration - System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP); - System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP); - - return new ComplexSamples(i, q, timestamp); - } -} diff --git a/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector128Bits.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector128Bits.java deleted file mode 100644 index 29a8a4b2..00000000 --- a/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector128Bits.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2025 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.buffer.sample; - -import io.github.dsheirer.sample.complex.ComplexSamples; -import java.util.Arrays; -import jdk.incubator.vector.FloatVector; -import jdk.incubator.vector.VectorOperators; -import jdk.incubator.vector.VectorSpecies; - -/** - * Vector SIMD implementation for non-packed native buffers - */ -public class SampleBufferIteratorVector128Bits extends SampleBufferIterator -{ - private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128; - - /** - * Constructs an instance - * - * @param samples from the device, either packed or unpacked. - * @param residualI samples from last buffer - * @param residualQ samples from last buffer - * @param averageDc measured - * @param timestamp of the buffer - * @param samplesPerMillisecond to calculate sub-buffer fragment timestamps - */ - public SampleBufferIteratorVector128Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, - long timestamp, float samplesPerMillisecond) - { - super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond); - } - - @Override - public ComplexSamples next() - { - if(mSamplesPointer >= mSamples.length) - { - throw new IllegalStateException("End of buffer exceeded"); - } - - long timestamp = getFragmentTimestamp(mSamplesPointer); - int offset = mSamplesPointer; - int fragmentPointer = 0; - float[] scaledSamples = new float[VECTOR_SPECIES.length()]; - - while(fragmentPointer < FRAGMENT_SIZE) - { - for(int y = 0; y < VECTOR_SPECIES.length(); y++) - { - //Couldn't figure out an easy way to load as a short vector and recast to a float vector - scaledSamples[y] = mSamples[offset++]; - } - - //SIMD scaling operation - FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0) - .sub(2048.0f) - .mul(SCALE_SIGNED_12_BIT_TO_FLOAT) - .sub(mAverageDc) - .intoArray(scaledSamples, 0); - - for(int y = 0; y < VECTOR_SPECIES.length(); y += 2) - { - mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y]; - mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1]; - } - } - - mSamplesPointer = offset; - - //The i line is a simple delay line, so we'll just copy those as a new array here. - float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE); - float[] q = new float[FRAGMENT_SIZE]; - - FloatVector accumulator; - FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0); - FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 4); - FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8); - FloatVector f4 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 12); - FloatVector f5 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16); - FloatVector f6 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 20); - - for(int x = 0; x < FRAGMENT_SIZE; x++) - { - accumulator = FloatVector.zero(VECTOR_SPECIES); - accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator); - accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator); - accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator); - accumulator = f4.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator); - accumulator = f5.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator); - accumulator = f6.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator); - - //Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc. - if(x % 2 == 0) - { - q[x] = accumulator.reduceLanes(VectorOperators.ADD); - } - else - { - i[x] = -i[x]; - q[x] = -accumulator.reduceLanes(VectorOperators.ADD); - } - } - - //Copy residual end samples to beginning of buffers for the next iteration - System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP); - System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP); - - return new ComplexSamples(i, q, timestamp); - } -} diff --git a/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector256Bits.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector256Bits.java deleted file mode 100644 index 9280c4cc..00000000 --- a/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector256Bits.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2025 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.buffer.sample; - -import io.github.dsheirer.sample.complex.ComplexSamples; -import java.util.Arrays; -import jdk.incubator.vector.FloatVector; -import jdk.incubator.vector.VectorOperators; -import jdk.incubator.vector.VectorSpecies; - -/** - * Vector SIMD implementation for non-packed native buffers - */ -public class SampleBufferIteratorVector256Bits extends SampleBufferIterator -{ - private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256; - - /** - * Constructs an instance - * - * @param samples from the device, either packed or unpacked. - * @param residualI samples from last buffer - * @param residualQ samples from last buffer - * @param averageDc measured - * @param timestamp of the buffer - * @param samplesPerMillisecond to calculate sub-buffer fragment timestamps - */ - public SampleBufferIteratorVector256Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, - long timestamp, float samplesPerMillisecond) - { - super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond); - } - - @Override - public ComplexSamples next() - { - if(mSamplesPointer >= mSamples.length) - { - throw new IllegalStateException("End of buffer exceeded"); - } - - long timestamp = getFragmentTimestamp(mSamplesPointer); - int offset = mSamplesPointer; - int fragmentPointer = 0; - float[] scaledSamples = new float[VECTOR_SPECIES.length()]; - - while(fragmentPointer < FRAGMENT_SIZE) - { - for(int y = 0; y < VECTOR_SPECIES.length(); y++) - { - //Couldn't figure out an easy way to load as a short vector and recast to a float vector - scaledSamples[y] = mSamples[offset++]; - } - - //SIMD scaling operation - FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0) - .sub(2048.0f) - .mul(SCALE_SIGNED_12_BIT_TO_FLOAT) - .sub(mAverageDc) - .intoArray(scaledSamples, 0); - - for(int y = 0; y < VECTOR_SPECIES.length(); y += 2) - { - mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y]; - mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1]; - } - } - - mSamplesPointer = offset; - - //The i line is a simple delay line, so we'll just copy those as a new array here. - float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE); - float[] q = new float[FRAGMENT_SIZE]; - - FloatVector accumulator; - FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0); - FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8); - FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16); - - for(int x = 0; x < FRAGMENT_SIZE; x++) - { - accumulator = FloatVector.zero(VECTOR_SPECIES); - accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator); - accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator); - accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator); - - //Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc. - if(x % 2 == 0) - { - q[x] = accumulator.reduceLanes(VectorOperators.ADD); - } - else - { - i[x] = -i[x]; - q[x] = -accumulator.reduceLanes(VectorOperators.ADD); - } - } - - //Copy residual end samples to beginning of buffers for the next iteration - System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP); - System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP); - - return new ComplexSamples(i, q, timestamp); - } -} diff --git a/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector512Bits.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector512Bits.java deleted file mode 100644 index ee7f844f..00000000 --- a/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector512Bits.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2025 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.buffer.sample; - -import io.github.dsheirer.sample.complex.ComplexSamples; -import java.util.Arrays; -import jdk.incubator.vector.FloatVector; -import jdk.incubator.vector.VectorOperators; -import jdk.incubator.vector.VectorSpecies; - -/** - * Vector SIMD implementation for non-packed native buffers - */ -public class SampleBufferIteratorVector512Bits extends SampleBufferIterator -{ - private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_512; - private float[] mFilterPart2; - - /** - * Constructs an instance - * - * @param samples from the device, either packed or unpacked. - * @param residualI samples from last buffer - * @param residualQ samples from last buffer - * @param averageDc measured - * @param timestamp of the buffer - * @param samplesPerMillisecond to calculate sub-buffer fragment timestamps - */ - public SampleBufferIteratorVector512Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, - long timestamp, float samplesPerMillisecond) - { - super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond); - //Arrange the last 8 taps of the filter into the higher indices of an array to align for SIMD - mFilterPart2 = new float[16]; - System.arraycopy(COEFFICIENTS, 16, mFilterPart2, 8, 8); - } - - @Override - public ComplexSamples next() - { - if(mSamplesPointer >= mSamples.length) - { - throw new IllegalStateException("End of buffer exceeded"); - } - - long timestamp = getFragmentTimestamp(mSamplesPointer); - - int offset = mSamplesPointer; - int fragmentPointer = 0; - float[] scaledSamples = new float[VECTOR_SPECIES.length()]; - - while(fragmentPointer < FRAGMENT_SIZE) - { - for(int y = 0; y < VECTOR_SPECIES.length(); y++) - { - //Couldn't figure out an easy way to load as a short vector and recast to a float vector - scaledSamples[y] = mSamples[offset++]; - } - - //SIMD scaling operation - FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0) - .sub(2048.0f) - .mul(SCALE_SIGNED_12_BIT_TO_FLOAT) - .sub(mAverageDc) - .intoArray(scaledSamples, 0); - - for(int y = 0; y < VECTOR_SPECIES.length(); y += 2) - { - mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y]; - mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1]; - } - } - - mSamplesPointer = offset; - - //The i line is a simple delay line, so we'll just copy those as a new array here. - float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE); - float[] q = new float[FRAGMENT_SIZE]; - - FloatVector accumulator; - FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0); - FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, mFilterPart2, 0); - - for(int x = 0; x < FRAGMENT_SIZE; x++) - { - accumulator = FloatVector.zero(VECTOR_SPECIES); - accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator); - accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator); - - //Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc. - if(x % 2 == 0) - { - q[x] = accumulator.reduceLanes(VectorOperators.ADD); - } - else - { - i[x] = -i[x]; - q[x] = -accumulator.reduceLanes(VectorOperators.ADD); - } - } - - //Copy residual end samples to beginning of buffers for the next iteration - System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP); - System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP); - - return new ComplexSamples(i, q, timestamp); - } -} diff --git a/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector64Bits.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector64Bits.java deleted file mode 100644 index dbaacfae..00000000 --- a/src/main/java/io/github/dsheirer/buffer/sample/SampleBufferIteratorVector64Bits.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2025 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.buffer.sample; - -import io.github.dsheirer.sample.complex.ComplexSamples; -import java.util.Arrays; -import jdk.incubator.vector.FloatVector; -import jdk.incubator.vector.VectorOperators; -import jdk.incubator.vector.VectorSpecies; - -/** - * Vector SIMD implementation for non-packed native buffers - */ -public class SampleBufferIteratorVector64Bits extends SampleBufferIterator -{ - private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64; - - /** - * Constructs an instance - * - * @param samples from the device, either packed or unpacked. - * @param residualI samples from last buffer - * @param residualQ samples from last buffer - * @param averageDc measured - * @param timestamp of the buffer - * @param samplesPerMillisecond to calculate sub-buffer fragment timestamps - */ - public SampleBufferIteratorVector64Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, - long timestamp, float samplesPerMillisecond) - { - super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond); - } - - @Override - public ComplexSamples next() - { - if(mSamplesPointer >= mSamples.length) - { - throw new IllegalStateException("End of buffer exceeded"); - } - - long timestamp = getFragmentTimestamp(mSamplesPointer); - - int offset = mSamplesPointer; - int fragmentPointer = 0; - float[] scaledSamples = new float[VECTOR_SPECIES.length()]; - - while(fragmentPointer < FRAGMENT_SIZE) - { - for(int y = 0; y < VECTOR_SPECIES.length(); y++) - { - //Couldn't figure out an easy way to load as a short vector and recast to a float vector - scaledSamples[y] = mSamples[offset++]; - } - - //SIMD scaling operation - FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0) - .sub(2048.0f) - .mul(SCALE_SIGNED_12_BIT_TO_FLOAT) - .sub(mAverageDc) - .intoArray(scaledSamples, 0); - - for(int y = 0; y < VECTOR_SPECIES.length(); y += 2) - { - mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y]; - mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1]; - } - } - - mSamplesPointer = offset; - - //The i line is a simple delay line, so we'll just copy those as a new array here. - float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE); - float[] q = new float[FRAGMENT_SIZE]; - - FloatVector accumulator; - FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0); - FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 2); - FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 4); - FloatVector f4 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 6); - FloatVector f5 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8); - FloatVector f6 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 10); - FloatVector f7 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 12); - FloatVector f8 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 14); - FloatVector f9 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16); - FloatVector f10 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 18); - FloatVector f11 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 20); - FloatVector f12 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 22); - - for(int x = 0; x < FRAGMENT_SIZE; x++) - { - accumulator = FloatVector.zero(VECTOR_SPECIES); - accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator); - accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 2), accumulator); - accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator); - accumulator = f4.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 6), accumulator); - accumulator = f5.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator); - accumulator = f6.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 10), accumulator); - accumulator = f7.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator); - accumulator = f8.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 14), accumulator); - accumulator = f9.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator); - accumulator = f10.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 18), accumulator); - accumulator = f11.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator); - accumulator = f12.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 22), accumulator); - - //Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc. - if(x % 2 == 0) - { - q[x] = accumulator.reduceLanes(VectorOperators.ADD); - } - else - { - i[x] = -i[x]; - q[x] = -accumulator.reduceLanes(VectorOperators.ADD); - } - } - - //Copy residual end samples to beginning of buffers for the next iteration - System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP); - System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP); - - return new ComplexSamples(i, q, timestamp); - } -} diff --git a/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorScalar.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorScalar.java deleted file mode 100644 index 0540d626..00000000 --- a/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorScalar.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2025 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.buffer.sample; - -import io.github.dsheirer.sample.complex.InterleavedComplexSamples; - -/** - * Scalar implementation for non-packed native buffers - */ -public class SampleInterleavedBufferIteratorScalar extends SampleBufferIterator -{ - /** - * Constructs an instance - * - * @param samples from the device, either packed or unpacked. - * @param residualI samples from last buffer - * @param residualQ samples from last buffer - * @param averageDc measured - * @param timestamp of the buffer - * @param samplesPerMillisecond to calculate sub-buffer fragment timestamps - */ - public SampleInterleavedBufferIteratorScalar(short[] samples, short[] residualI, short[] residualQ, float averageDc, - long timestamp, float samplesPerMillisecond) - { - super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond); - } - - @Override - public InterleavedComplexSamples next() - { - if(mSamplesPointer >= mSamples.length) - { - throw new IllegalStateException("End of buffer exceeded"); - } - - long timestamp = getFragmentTimestamp(mSamplesPointer); - int offset = mSamplesPointer; - - for(int x = 0; x < FRAGMENT_SIZE; x++) - { - mIBuffer[x + I_OVERLAP] = scale(mSamples[offset++], mAverageDc); - mQBuffer[x + Q_OVERLAP] = scale(mSamples[offset++], mAverageDc); - } - - mSamplesPointer = offset; - - float[] samples = new float[FRAGMENT_SIZE * 2]; - - float accumulator; - - for(int x = 0; x < FRAGMENT_SIZE; x++) - { - accumulator = 0; - - for(int tap = 0; tap < COEFFICIENTS.length; tap++) - { - accumulator += COEFFICIENTS[tap] * mQBuffer[x + tap]; - } - - //Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc. - if(x % 2 == 0) - { - samples[2 * x] = mIBuffer[x]; - samples[2 * x + 1] = accumulator; - } - else - { - samples[2 * x] = -mIBuffer[x]; - samples[2 * x + 1] = -accumulator; - } - } - - //Copy residual end samples to beginning of buffers for the next iteration - System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP); - System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP); - - return new InterleavedComplexSamples(samples, timestamp); - } -} diff --git a/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector128Bits.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector128Bits.java deleted file mode 100644 index 205656e2..00000000 --- a/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector128Bits.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2025 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.buffer.sample; - -import io.github.dsheirer.sample.complex.InterleavedComplexSamples; -import jdk.incubator.vector.FloatVector; -import jdk.incubator.vector.VectorOperators; -import jdk.incubator.vector.VectorSpecies; - -/** - * Vector SIMD implementation for non-packed native buffers - */ -public class SampleInterleavedBufferIteratorVector128Bits extends SampleBufferIterator -{ - private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128; - - /** - * Constructs an instance - * - * @param samples from the device, either packed or unpacked. - * @param residualI samples from last buffer - * @param residualQ samples from last buffer - * @param averageDc measured - * @param timestamp of the buffer - * @param samplesPerMillisecond to calculate sub-buffer fragment timestamps - */ - public SampleInterleavedBufferIteratorVector128Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, - long timestamp, float samplesPerMillisecond) - { - super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond); - } - - @Override - public InterleavedComplexSamples next() - { - if(mSamplesPointer >= mSamples.length) - { - throw new IllegalStateException("End of buffer exceeded"); - } - - long timestamp = getFragmentTimestamp(mSamplesPointer); - int offset = mSamplesPointer; - int fragmentPointer = 0; - float[] scaledSamples = new float[VECTOR_SPECIES.length()]; - - while(fragmentPointer < FRAGMENT_SIZE) - { - for(int y = 0; y < VECTOR_SPECIES.length(); y++) - { - //Couldn't figure out an easy way to load as a short vector and recast to a float vector - scaledSamples[y] = mSamples[offset++]; - } - - //SIMD scaling operation - FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0) - .sub(2048.0f) - .mul(SCALE_SIGNED_12_BIT_TO_FLOAT) - .sub(mAverageDc) - .intoArray(scaledSamples, 0); - - for(int y = 0; y < VECTOR_SPECIES.length(); y += 2) - { - mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y]; - mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1]; - } - } - - mSamplesPointer = offset; - - float[] samples = new float[FRAGMENT_SIZE * 2]; - FloatVector accumulator; - FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0); - FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 4); - FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8); - FloatVector f4 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 12); - FloatVector f5 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16); - FloatVector f6 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 20); - - for(int x = 0; x < FRAGMENT_SIZE; x++) - { - accumulator = FloatVector.zero(VECTOR_SPECIES); - accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator); - accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator); - accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator); - accumulator = f4.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator); - accumulator = f5.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator); - accumulator = f6.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator); - - //Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc. - if(x % 2 == 0) - { - samples[2 * x] = mIBuffer[x]; - samples[2 * x + 1] = accumulator.reduceLanes(VectorOperators.ADD); - } - else - { - samples[2 * x] = -mIBuffer[x]; - samples[2 * x + 1] = -accumulator.reduceLanes(VectorOperators.ADD); - } - } - - //Copy residual end samples to beginning of buffers for the next iteration - System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP); - System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP); - - return new InterleavedComplexSamples(samples, timestamp); - } -} diff --git a/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector256Bits.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector256Bits.java deleted file mode 100644 index 3951472a..00000000 --- a/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector256Bits.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2025 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.buffer.sample; - -import io.github.dsheirer.sample.complex.InterleavedComplexSamples; -import jdk.incubator.vector.FloatVector; -import jdk.incubator.vector.VectorOperators; -import jdk.incubator.vector.VectorSpecies; - -/** - * Vector SIMD implementation for non-packed native buffers - */ -public class SampleInterleavedBufferIteratorVector256Bits extends SampleBufferIterator -{ - private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256; - - /** - * Constructs an instance - * - * @param samples from the device, either packed or unpacked. - * @param residualI samples from last buffer - * @param residualQ samples from last buffer - * @param averageDc measured - * @param timestamp of the buffer - * @param samplesPerMillisecond to calculate sub-buffer fragment timestamps - */ - public SampleInterleavedBufferIteratorVector256Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, - long timestamp, float samplesPerMillisecond) - { - super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond); - } - - @Override - public InterleavedComplexSamples next() - { - if(mSamplesPointer >= mSamples.length) - { - throw new IllegalStateException("End of buffer exceeded"); - } - - long timestamp = getFragmentTimestamp(mSamplesPointer); - int offset = mSamplesPointer; - int fragmentPointer = 0; - float[] scaledSamples = new float[VECTOR_SPECIES.length()]; - - while(fragmentPointer < FRAGMENT_SIZE) - { - for(int y = 0; y < VECTOR_SPECIES.length(); y++) - { - //Couldn't figure out an easy way to load as a short vector and recast to a float vector - scaledSamples[y] = mSamples[offset++]; - } - - //SIMD scaling operation - FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0) - .sub(2048.0f) - .mul(SCALE_SIGNED_12_BIT_TO_FLOAT) - .sub(mAverageDc) - .intoArray(scaledSamples, 0); - - for(int y = 0; y < VECTOR_SPECIES.length(); y += 2) - { - mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y]; - mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1]; - } - } - - mSamplesPointer = offset; - - float[] samples = new float[FRAGMENT_SIZE * 2]; - FloatVector accumulator; - FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0); - FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8); - FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16); - - for(int x = 0; x < FRAGMENT_SIZE; x++) - { - accumulator = FloatVector.zero(VECTOR_SPECIES); - accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator); - accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator); - accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator); - - //Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc. - if(x % 2 == 0) - { - samples[2 * x] = mIBuffer[x]; - samples[2 * x + 1] = accumulator.reduceLanes(VectorOperators.ADD); - } - else - { - samples[2 * x] = -mIBuffer[x]; - samples[2 * x + 1] = -accumulator.reduceLanes(VectorOperators.ADD); - } - } - - //Copy residual end samples to beginning of buffers for the next iteration - System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP); - System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP); - - return new InterleavedComplexSamples(samples, timestamp); - } -} diff --git a/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector512Bits.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector512Bits.java deleted file mode 100644 index cf6df573..00000000 --- a/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector512Bits.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2025 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.buffer.sample; - -import io.github.dsheirer.sample.complex.InterleavedComplexSamples; -import jdk.incubator.vector.FloatVector; -import jdk.incubator.vector.VectorOperators; -import jdk.incubator.vector.VectorSpecies; - -/** - * Vector SIMD implementation for non-packed native buffers - */ -public class SampleInterleavedBufferIteratorVector512Bits extends SampleBufferIterator -{ - private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_512; - private float[] mFilterPart2; - - /** - * Constructs an instance - * - * @param samples from the device, either packed or unpacked. - * @param residualI samples from last buffer - * @param residualQ samples from last buffer - * @param averageDc measured - * @param timestamp of the buffer - * @param samplesPerMillisecond to calculate sub-buffer fragment timestamps - */ - public SampleInterleavedBufferIteratorVector512Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, - long timestamp, float samplesPerMillisecond) - { - super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond); - - //Arrange the last 8 taps of the filter into the higher indices of an array to align for SIMD - mFilterPart2 = new float[16]; - System.arraycopy(COEFFICIENTS, 16, mFilterPart2, 8, 8); - } - - @Override - public InterleavedComplexSamples next() - { - if(mSamplesPointer >= mSamples.length) - { - throw new IllegalStateException("End of buffer exceeded"); - } - - long timestamp = getFragmentTimestamp(mSamplesPointer); - int offset = mSamplesPointer; - int fragmentPointer = 0; - float[] scaledSamples = new float[VECTOR_SPECIES.length()]; - - while(fragmentPointer < FRAGMENT_SIZE) - { - for(int y = 0; y < VECTOR_SPECIES.length(); y++) - { - //Couldn't figure out an easy way to load as a short vector and recast to a float vector - scaledSamples[y] = mSamples[offset++]; - } - - //SIMD scaling operation - FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0) - .sub(2048.0f) - .mul(SCALE_SIGNED_12_BIT_TO_FLOAT) - .sub(mAverageDc) - .intoArray(scaledSamples, 0); - - for(int y = 0; y < VECTOR_SPECIES.length(); y += 2) - { - mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y]; - mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1]; - } - } - - mSamplesPointer = offset; - - float[] samples = new float[FRAGMENT_SIZE * 2]; - FloatVector accumulator; - FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0); - FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, mFilterPart2, 0); - - for(int x = 0; x < FRAGMENT_SIZE; x++) - { - accumulator = FloatVector.zero(VECTOR_SPECIES); - accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator); - accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator); - - //Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc. - if(x % 2 == 0) - { - samples[2 * x] = mIBuffer[x]; - samples[2 * x + 1] = accumulator.reduceLanes(VectorOperators.ADD); - } - else - { - samples[2 * x] = -mIBuffer[x]; - samples[2 * x + 1] = -accumulator.reduceLanes(VectorOperators.ADD); - } - } - - //Copy residual end samples to beginning of buffers for the next iteration - System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP); - System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP); - - return new InterleavedComplexSamples(samples, timestamp); - } -} diff --git a/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector64Bits.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector64Bits.java deleted file mode 100644 index 8859565a..00000000 --- a/src/main/java/io/github/dsheirer/buffer/sample/SampleInterleavedBufferIteratorVector64Bits.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2025 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.buffer.sample; - -import io.github.dsheirer.sample.complex.InterleavedComplexSamples; -import jdk.incubator.vector.FloatVector; -import jdk.incubator.vector.VectorOperators; -import jdk.incubator.vector.VectorSpecies; - -/** - * Vector SIMD implementation for non-packed native buffers - */ -public class SampleInterleavedBufferIteratorVector64Bits extends SampleBufferIterator -{ - private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64; - - /** - * Constructs an instance - * - * @param samples from the device, either packed or unpacked. - * @param residualI samples from last buffer - * @param residualQ samples from last buffer - * @param averageDc measured - * @param timestamp of the buffer - * @param samplesPerMillisecond to calculate sub-buffer fragment timestamps - */ - public SampleInterleavedBufferIteratorVector64Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, - long timestamp, float samplesPerMillisecond) - { - super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond); - } - - @Override - public InterleavedComplexSamples next() - { - if(mSamplesPointer >= mSamples.length) - { - throw new IllegalStateException("End of buffer exceeded"); - } - - long timestamp = getFragmentTimestamp(mSamplesPointer); - int offset = mSamplesPointer; - int fragmentPointer = 0; - float[] scaledSamples = new float[VECTOR_SPECIES.length()]; - - while(fragmentPointer < FRAGMENT_SIZE) - { - for(int y = 0; y < VECTOR_SPECIES.length(); y++) - { - //Couldn't figure out an easy way to load as a short vector and recast to a float vector - scaledSamples[y] = mSamples[offset++]; - } - - //SIMD scaling operation - FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0) - .sub(2048.0f) - .mul(SCALE_SIGNED_12_BIT_TO_FLOAT) - .sub(mAverageDc) - .intoArray(scaledSamples, 0); - - for(int y = 0; y < VECTOR_SPECIES.length(); y += 2) - { - mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y]; - mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1]; - } - } - - mSamplesPointer = offset; - - float[] samples = new float[FRAGMENT_SIZE * 2]; - FloatVector accumulator; - FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0); - FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 2); - FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 4); - FloatVector f4 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 6); - FloatVector f5 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8); - FloatVector f6 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 10); - FloatVector f7 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 12); - FloatVector f8 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 14); - FloatVector f9 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16); - FloatVector f10 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 18); - FloatVector f11 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 20); - FloatVector f12 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 22); - - for(int x = 0; x < FRAGMENT_SIZE; x++) - { - accumulator = FloatVector.zero(VECTOR_SPECIES); - accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator); - accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 2), accumulator); - accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator); - accumulator = f4.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 6), accumulator); - accumulator = f5.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator); - accumulator = f6.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 10), accumulator); - accumulator = f7.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator); - accumulator = f8.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 14), accumulator); - accumulator = f9.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator); - accumulator = f10.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 18), accumulator); - accumulator = f11.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator); - accumulator = f12.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 22), accumulator); - - //Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc. - if(x % 2 == 0) - { - samples[2 * x] = mIBuffer[x]; - samples[2 * x + 1] = accumulator.reduceLanes(VectorOperators.ADD); - } - else - { - samples[2 * x] = -mIBuffer[x]; - samples[2 * x + 1] = -accumulator.reduceLanes(VectorOperators.ADD); - } - } - - //Copy residual end samples to beginning of buffers for the next iteration - System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP); - System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP); - - return new InterleavedComplexSamples(samples, timestamp); - } -} diff --git a/src/main/java/io/github/dsheirer/buffer/sample/SampleNativeBuffer.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleNativeBuffer.java deleted file mode 100644 index 9554b2a9..00000000 --- a/src/main/java/io/github/dsheirer/buffer/sample/SampleNativeBuffer.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2026 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.buffer.sample; - -import io.github.dsheirer.buffer.AbstractNativeBuffer; -import io.github.dsheirer.sample.complex.ComplexSamples; -import io.github.dsheirer.sample.complex.InterleavedComplexSamples; -import io.github.dsheirer.vector.calibrate.Implementation; -import java.util.Iterator; - -/** - * Native buffer scalar implementation for non-packed samples. - */ -public class SampleNativeBuffer extends AbstractNativeBuffer -{ - private final short[] mSamples; - private final short[] mResidualI; - private final short[] mResidualQ; - private final float mAverageDc; - private final Implementation mInterleavedImplementation; - private final Implementation mNonInterleavedImplementation; - - /** - * Constructs an instance - * @param samples (non-packed) from the device - * @param residualI samples from previous buffer - * @param residualQ samples from previous buffer - * @param averageDc measured - * @param timestamp of the buffer - * @param interleavedImplementation optimal, scalar vs vector SIMD - * @param nonInterleavedImplementation optimal, scalar vs vector SIMD - * @param samplesPerMillisecond used to calculate sub-buffer fragment timestamp offsets from the start of this buffer. - */ - public SampleNativeBuffer(short[] samples, short[] residualI, short[] residualQ, float averageDc, - long timestamp, Implementation interleavedImplementation, - Implementation nonInterleavedImplementation, float samplesPerMillisecond) - { - super(timestamp, samplesPerMillisecond); - - //Ensure we're an even multiple of the fragment size. Typically, this will be 64k or 128k - if(samples.length % SampleBufferIterator.FRAGMENT_SIZE != 0) - { - throw new IllegalArgumentException("Samples short[] length [" + samples.length + - "] must be an even multiple of " + SampleBufferIterator.FRAGMENT_SIZE); - } - - mSamples = samples; - mResidualI = residualI; - mResidualQ = residualQ; - mAverageDc = averageDc; - mInterleavedImplementation = interleavedImplementation; - mNonInterleavedImplementation = nonInterleavedImplementation; - } - - @Override - public int sampleCount() - { - return mSamples.length / 2; - } - - @Override - public Iterator iterator() - { - return switch(mNonInterleavedImplementation) - { - case VECTOR_SIMD_512 -> new SampleBufferIteratorVector512Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond()); - case VECTOR_SIMD_256-> new SampleBufferIteratorVector256Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond()); - case VECTOR_SIMD_128-> new SampleBufferIteratorVector128Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond()); - case VECTOR_SIMD_64 ->new SampleBufferIteratorVector64Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond()); - default -> new SampleBufferIteratorScalar(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond()); - }; - } - - @Override - public Iterator iteratorInterleaved() - { - return switch(mInterleavedImplementation) - { - case VECTOR_SIMD_512 -> new SampleInterleavedBufferIteratorVector512Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond()); - case VECTOR_SIMD_256-> new SampleInterleavedBufferIteratorVector256Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond()); - case VECTOR_SIMD_128-> new SampleInterleavedBufferIteratorVector128Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond()); - case VECTOR_SIMD_64 ->new SampleInterleavedBufferIteratorVector64Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond()); - default -> new SampleInterleavedBufferIteratorScalar(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond()); - }; - } -} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/buffer/sample/SampleNativeBufferFactory.java b/src/main/java/io/github/dsheirer/buffer/sample/SampleNativeBufferFactory.java deleted file mode 100644 index 0efea14c..00000000 --- a/src/main/java/io/github/dsheirer/buffer/sample/SampleNativeBufferFactory.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2025 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.buffer.sample; - -import io.github.dsheirer.buffer.AbstractNativeBufferFactory; -import io.github.dsheirer.buffer.INativeBuffer; -import io.github.dsheirer.vector.calibrate.CalibrationManager; -import io.github.dsheirer.vector.calibrate.CalibrationType; -import io.github.dsheirer.vector.calibrate.Implementation; -import java.nio.ByteBuffer; -import java.util.Arrays; - -/** - * Implements a factory for creating SignedByteNativeBuffer instances - */ -public class SampleNativeBufferFactory extends AbstractNativeBufferFactory -{ - private boolean mSamplePacking = false; - private short[] mResidualI = new short[SampleBufferIterator.I_OVERLAP]; - private short[] mResidualQ = new short[SampleBufferIterator.Q_OVERLAP]; - private ISampleConverter mConverter; - private Implementation mInterleavedIteratorImplementation; - private Implementation mNonInterleavedIteratorImplementation; - - /** - * Constructs an instance - */ - public SampleNativeBufferFactory() - { - updateConverter(); - - mInterleavedIteratorImplementation = CalibrationManager.getInstance() - .getImplementation(CalibrationType.SAMPLE_UNPACKED_INTERLEAVED_ITERATOR); - mNonInterleavedIteratorImplementation = CalibrationManager.getInstance() - .getImplementation(CalibrationType.SAMPLE_UNPACKED_ITERATOR); - } - - /** - * Sample packing places two 12-bit samples into 3 bytes when enabled or - * places two 12-bit samples into 4 bytes when disabled. - * - * @param enabled - */ - public void setSamplePacking(boolean enabled) - { - mSamplePacking = enabled; - updateConverter(); - } - - /** - * Creates an optimal sample converter instance. - */ - private void updateConverter() - { - if(mSamplePacking) - { - if(!(mConverter instanceof ScalarPackedSampleConverter)) - { - mConverter = new ScalarPackedSampleConverter(); - } - } - else - { - Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.SAMPLE_PACKED_CONVERTER); - - if(implementation == Implementation.VECTOR_SIMD_PREFERRED) - { - if(!(mConverter instanceof VectorUnpackedSampleConverter)) - { - mConverter = new VectorUnpackedSampleConverter(); - } - } - else - { - if(!(mConverter instanceof ScalarUnpackedSampleConverter)) - { - mConverter = new ScalarUnpackedSampleConverter(); - } - } - } - } - - @Override - public INativeBuffer getBuffer(ByteBuffer buffer, long timestamp) - { - short[] samples = mConverter.convert(buffer); - - INativeBuffer nativeBuffer = new SampleNativeBuffer(samples, - Arrays.copyOf(mResidualI, mResidualI.length), - Arrays.copyOf(mResidualQ, mResidualQ.length), mConverter.getAverageDc(), timestamp, - mInterleavedIteratorImplementation, mNonInterleavedIteratorImplementation, getSamplesPerMillisecond()); - - extractResidual(samples); - - return nativeBuffer; - } - - /** - * Extracts the residual overlap samples needed for continuity in the Hilbert transform filter. - * @param samples to extract residual from - */ - private void extractResidual(short[] samples) - { - int offset = samples.length - (SampleBufferIterator.I_OVERLAP * 2); - - for(int i = 0; i < SampleBufferIterator.I_OVERLAP; i++) - { - mResidualI[i] = samples[offset + (2 * i)]; - } - - offset = samples.length - (SampleBufferIterator.Q_OVERLAP * 2) + 1; - - for(int q = 0; q < SampleBufferIterator.Q_OVERLAP; q++) - { - mResidualQ[q] = samples[offset + (2 * q)]; - } - } -} diff --git a/src/main/java/io/github/dsheirer/buffer/sample/ScalarPackedSampleConverter.java b/src/main/java/io/github/dsheirer/buffer/sample/ScalarPackedSampleConverter.java deleted file mode 100644 index 7bcd3a7c..00000000 --- a/src/main/java/io/github/dsheirer/buffer/sample/ScalarPackedSampleConverter.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2025 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.buffer.sample; - -import io.github.dsheirer.buffer.DcCorrectionManager; -import java.nio.ByteBuffer; - -/** - * Scalar implementation of sample converter for un-packed samples. - */ -public class ScalarPackedSampleConverter implements ISampleConverter -{ - /** - * Manages DC calculations and processing interval - */ - private DcCorrectionManager mDcCalculationManager = new DcCorrectionManager(); - - @Override - public short[] convert(ByteBuffer buffer) - { - boolean shouldCalculateDc = mDcCalculationManager.shouldCalculateDc(); - - int offset = 0; - short sample; - short[] samples; - byte b1, b2, b3; - - samples = new short[buffer.capacity() / 3 * 2]; - - if(shouldCalculateDc) - { - long dcAccumulator = 0; - - for(int x = 0; x < samples.length; x += 2) - { - b1 = buffer.get(offset++); - b2 = buffer.get(offset++); - b3 = buffer.get(offset++); - - sample = (short)(((b1 & 0xFF) << 4) | ((b2 & 0xF0) >> 4)); - samples[x] = sample; - dcAccumulator += sample; - - sample = (short)(((b2 & 0xF) << 8) | (b3 & 0xFF)); - samples[x] = sample; - dcAccumulator += sample; - } - - float averageDcNow = ((float)dcAccumulator / (float)samples.length) - 2048.0f; - averageDcNow *= SampleBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT; - mDcCalculationManager.adjust(averageDcNow); - } - else - { - for(int x = 0; x < samples.length; x += 2) - { - b1 = buffer.get(offset++); - b2 = buffer.get(offset++); - b3 = buffer.get(offset++); - - sample = (short)(((b1 & 0xFF) << 4) | ((b2 & 0xF0) >> 4)); - samples[x] = sample; - - sample = (short)(((b2 & 0xF) << 8) | (b3 & 0xFF)); - samples[x] = sample; - } - } - - return samples; - } - - @Override - public float getAverageDc() - { - return mDcCalculationManager.getAverageDc(); - } - - public static void main(String[] args) - { - byte[] raw = new byte[262144]; - ByteBuffer buffer = ByteBuffer.wrap(raw); - - ScalarPackedSampleConverter scalar = new ScalarPackedSampleConverter(); - VectorUnpackedSampleConverter vector = new VectorUnpackedSampleConverter(); - - long accumulator = 0; - int iterations = 1_000_000; - - System.out.println("Starting ..."); - long start = System.currentTimeMillis(); - for(int count = 0; count < iterations; count++) - { -// short[] samples = scalar.convert(buffer); - short[] samples = vector.convert(buffer); - accumulator += samples[1]; - } - long elapsed = System.currentTimeMillis() - start; - - System.out.println("Accumulator: " + accumulator); - System.out.println("Elapsed: " + (elapsed / 1000.0f)); - System.out.println("Finished"); - } -} diff --git a/src/main/java/io/github/dsheirer/buffer/sample/ScalarUnpackedSampleConverter.java b/src/main/java/io/github/dsheirer/buffer/sample/ScalarUnpackedSampleConverter.java deleted file mode 100644 index 50ad184f..00000000 --- a/src/main/java/io/github/dsheirer/buffer/sample/ScalarUnpackedSampleConverter.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2025 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.buffer.sample; - -import io.github.dsheirer.buffer.DcCorrectionManager; -import java.nio.ByteBuffer; - -/** - * Scalar implementation of sample converter for un-packed samples. - */ -public class ScalarUnpackedSampleConverter implements ISampleConverter -{ - /** - * Manages DC calculations and processing interval - */ - private DcCorrectionManager mDcCalculationManager = new DcCorrectionManager(); - - @Override - public short[] convert(ByteBuffer buffer) - { - boolean shouldCalculateDc = mDcCalculationManager.shouldCalculateDc(); - - int offset = 0; - short sample; - short[] samples; - byte b1, b2; - - samples = new short[buffer.capacity() / 2]; - - if(shouldCalculateDc) - { - long dcAccumulator = 0; - - for(int x = 0; x < samples.length; x++) - { - b1 = buffer.get(offset++); - b2 = buffer.get(offset++); - sample = (short)(((b2 & 0x0F) << 8) | (b1 & 0xFF)); - samples[x] = sample; - dcAccumulator += sample; - } - - float averageDcNow = ((float)dcAccumulator / (float)samples.length) - 2048.0f; - averageDcNow *= SampleBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT; - mDcCalculationManager.adjust(averageDcNow); - } - else - { - for(int x = 0; x < samples.length; x++) - { - b1 = buffer.get(offset++); - b2 = buffer.get(offset++); - sample = (short)(((b2 & 0x0F) << 8) | (b1 & 0xFF)); - samples[x] = sample; - } - } - - return samples; - } - - @Override - public float getAverageDc() - { - return mDcCalculationManager.getAverageDc(); - } -} diff --git a/src/main/java/io/github/dsheirer/buffer/sample/VectorUnpackedSampleConverter.java b/src/main/java/io/github/dsheirer/buffer/sample/VectorUnpackedSampleConverter.java deleted file mode 100644 index 9c018207..00000000 --- a/src/main/java/io/github/dsheirer/buffer/sample/VectorUnpackedSampleConverter.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2025 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.buffer.sample; - -import io.github.dsheirer.buffer.DcCorrectionManager; -import java.nio.ByteBuffer; -import jdk.incubator.vector.ShortVector; -import jdk.incubator.vector.VectorOperators; -import jdk.incubator.vector.VectorSpecies; - -/** - * Scalar implementation of sample converter for un-packed samples. - */ -public class VectorUnpackedSampleConverter implements ISampleConverter -{ - private static final VectorSpecies VECTOR_SPECIES = ShortVector.SPECIES_PREFERRED; - - /** - * Manages DC calculations and processing interval - */ - private DcCorrectionManager mDcCalculationManager = new DcCorrectionManager(); - - @Override - public short[] convert(ByteBuffer buffer) - { - boolean shouldCalculateDc = mDcCalculationManager.shouldCalculateDc(); - - short sample; - short[] samples; - byte b1, b2; - - samples = new short[buffer.capacity() / 2]; - - int bytesOffset; - int rawPointer = 0; - ShortVector vector; - - int samplesOffset = 0; - - short[] bytes1 = new short[VECTOR_SPECIES.length()]; - short[] bytes2 = new short[VECTOR_SPECIES.length()]; - - if(shouldCalculateDc) - { - long dcAccumulator = 0; - - for(; samplesOffset < VECTOR_SPECIES.loopBound(samples.length); samplesOffset += VECTOR_SPECIES.length()) - { - bytesOffset = 0; - - while(bytesOffset < VECTOR_SPECIES.length()) - { - bytes1[bytesOffset] = (short)(buffer.get(rawPointer++) & 0xFF); - bytes2[bytesOffset++] = (short)(buffer.get(rawPointer++) & 0x0F); - } - - vector = ShortVector.fromArray(VECTOR_SPECIES, bytes1, 0) - .or(ShortVector.fromArray(VECTOR_SPECIES, bytes2, 0).lanewise(VectorOperators.LSHL, 8)); - dcAccumulator += vector.reduceLanes(VectorOperators.ADD); - vector.intoArray(samples, samplesOffset); - } - - for(; samplesOffset < samples.length; samplesOffset++) - { - b1 = buffer.get(rawPointer++); - b2 = buffer.get(rawPointer++); - - sample = (short)(((b2 & 0x0F) << 8) | (b1 & 0xFF)); - samples[samplesOffset] = sample; - dcAccumulator += sample; - } - - float averageDcNow = ((float)dcAccumulator / (float)samples.length) - 2048.0f; - averageDcNow *= SampleBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT; - mDcCalculationManager.adjust(averageDcNow); - } - else - { - for(; samplesOffset < VECTOR_SPECIES.loopBound(samples.length); samplesOffset += VECTOR_SPECIES.length()) - { - bytesOffset = 0; - - while(bytesOffset < VECTOR_SPECIES.length()) - { - bytes1[bytesOffset] = (short)(buffer.get(rawPointer++) & 0xFF); - bytes2[bytesOffset++] = (short)(buffer.get(rawPointer++) & 0x0F); - } - - vector = ShortVector.fromArray(VECTOR_SPECIES, bytes1, 0) - .or(ShortVector.fromArray(VECTOR_SPECIES, bytes2, 0).lanewise(VectorOperators.LSHL, 8)); - vector.intoArray(samples, samplesOffset); - } - - for(; samplesOffset < samples.length; samplesOffset++) - { - b1 = buffer.get(rawPointer++); - b2 = buffer.get(rawPointer++); - - sample = (short)(((b2 & 0x0F) << 8) | (b1 & 0xFF)); - samples[samplesOffset] = sample; - } - } - - return samples; - } - - @Override - public float getAverageDc() - { - return mDcCalculationManager.getAverageDc(); - } -} diff --git a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyDeviceInformation.java b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyDeviceInformation.java index 2b6b2be3..8bb05538 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyDeviceInformation.java +++ b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyDeviceInformation.java @@ -1,157 +1,102 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2026 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + package io.github.dsheirer.source.tuner.airspy; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Arrays; +import io.github.dsheirer.airspy.PartIdSerialNumber; +/** + * Airspy device information, read from the device by libairspy. + */ public class AirspyDeviceInformation { - private final static Logger mLog = - LoggerFactory.getLogger( AirspyDeviceInformation.class ); - - private AirspyTunerController.BoardID mBoardID = AirspyTunerController.BoardID.UNKNOWN; - private String mFirmwareVersion = "Unknown"; - private String mPartNumber = "Unknown"; - private String mSerialNumber = "Unknown"; + private final String mBoardName; + private final String mFirmwareVersion; + private final PartIdSerialNumber mPartIdSerialNumber; /** - * Airspy Device Information - provides parsing and access to device - * information components. + * Constructs an instance + * @param boardName from the device + * @param firmwareVersion string from the device + * @param partIdSerialNumber from the device */ - public AirspyDeviceInformation() + public AirspyDeviceInformation(String boardName, String firmwareVersion, PartIdSerialNumber partIdSerialNumber) { + mBoardName = boardName; + mFirmwareVersion = firmwareVersion; + mPartIdSerialNumber = partIdSerialNumber; } - - public void setBoardID( int boardID ) + + public String getBoardName() { - mBoardID = AirspyTunerController.BoardID.fromValue( boardID ); + return mBoardName; } - - public AirspyTunerController.BoardID getBoardID() - { - return mBoardID; - } - - public void setVersion( byte[] data ) - { - if( data != null && data.length > 0 ) - { - /* Truncate the version string if we have a line break - ASCII 10 */ - for( int x = 0; x < data.length; x++ ) - { - if( data[ x ] == (byte)10 ) - { - mFirmwareVersion = new String( Arrays.copyOf( data, x ) ); - mFirmwareVersion = mFirmwareVersion.replace( "AirSpy NOS ", "" ); - return; - } - } - - mFirmwareVersion = new String( data ); - mFirmwareVersion = mFirmwareVersion.replace( "AirSpy NOS ", "" ); - } - else - { - mLog.error( "Error setting airspy version byte data - null:" + - ( data == null ? " TRUE" : " FALSE length:" + data.length ) ); - } - } - + + /** + * Firmware version, without the "AirSpy NOS " prefix and truncated at the first line break. + */ public String getVersion() { - return mFirmwareVersion; - } - - public void setPartAndSerialNumber( byte[] data ) - { - if( data != null && data.length == 24 ) + if(mFirmwareVersion == null) { - //Note: values are byte-reversed (big-endian) 32-bit chunks - StringBuilder part = new StringBuilder(); - - part.append( format( data[ 3 ] ) ); - part.append( format( data[ 2 ] ) ); - part.append( format( data[ 1 ] ) ); - part.append( format( data[ 0 ] ) ); - part.append( format( data[ 7 ] ) ); - part.append( format( data[ 6 ] ) ); - part.append( format( data[ 5 ] ) ); - part.append( format( data[ 4 ] ) ); - - mPartNumber = part.toString(); - - StringBuilder serial = new StringBuilder(); - - //Note: current airspy library only exposes 64-bits of serial number - //address space, but appears to have room for 128-bits. We won't - //add the first 64 bits if it's zeros - if( !( data[ 8 ] == 0 && data[ 9 ] == 0 && data[ 10 ] == 0 && data[ 11 ] == 0 ) ) - { - serial.append( format( data[ 11 ] ) ); - serial.append( format( data[ 10 ] ) ); - serial.append( format( data[ 9 ] ) ); - serial.append( format( data[ 8 ] ) ); - serial.append( "-" ); - } - - if( !( data[ 12 ] == 0 && data[ 13 ] == 0 && data[ 14 ] == 0 && data[ 15 ] == 0 ) ) - { - serial.append( format( data[ 15 ] ) ); - serial.append( format( data[ 14 ] ) ); - serial.append( format( data[ 13 ] ) ); - serial.append( format( data[ 12 ] ) ); - serial.append( "-" ); - } - - serial.append( format( data[ 19 ] ) ); - serial.append( format( data[ 18 ] ) ); - serial.append( format( data[ 17 ] ) ); - serial.append( format( data[ 16 ] ) ); - serial.append( "-" ); - serial.append( format( data[ 23 ] ) ); - serial.append( format( data[ 22 ] ) ); - serial.append( format( data[ 21 ] ) ); - serial.append( format( data[ 20 ] ) ); - - mSerialNumber = serial.toString(); + return "Unknown"; } - else + + String version = mFirmwareVersion; + int lineBreak = version.indexOf('\n'); + + if(lineBreak >= 0) { - mLog.error( "Error setting airspy part and serial byte data - null:" + - ( data == null ? " TRUE" : " FALSE length:" + data.length ) ); + version = version.substring(0, lineBreak); } + + return version.replace("AirSpy NOS ", "").trim(); } - - private String format( byte value ) - { - return String.format( "%02X", value ); - } - + + /** + * Serial number, formatted as the identifier that tuner configurations are saved under. + */ public String getSerialNumber() { - return mSerialNumber; + long serial = mPartIdSerialNumber.serialNumber(); + return String.format("%08X-%08X", serial >>> 32, serial & 0xFFFFFFFFL); } - + public String getPartNumber() { - return mPartNumber; + return String.format("%08X%08X", mPartIdSerialNumber.partId1(), mPartIdSerialNumber.partId2()); } - + public String toString() { StringBuilder sb = new StringBuilder(); - sb.append( "Airspy Device Information\n" ); - sb.append( "Board: " ); - sb.append( getBoardID().getLabel() ); - sb.append( "\nPart Number: " ); - sb.append( getPartNumber() ); - sb.append( "\nSerial Number: " ); - sb.append( getSerialNumber() ); - sb.append( "\nFirmware: " ); - sb.append( getVersion() ); - + sb.append("Airspy Device Information\n"); + sb.append("Board: "); + sb.append(getBoardName()); + sb.append("\nPart Number: "); + sb.append(getPartNumber()); + sb.append("\nSerial Number: "); + sb.append(getSerialNumber()); + sb.append("\nFirmware: "); + sb.append(getVersion()); + return sb.toString(); } } diff --git a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTuner.java b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTuner.java index 17014fac..7c9a5248 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTuner.java +++ b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTuner.java @@ -22,7 +22,6 @@ import io.github.dsheirer.preference.source.ChannelizerType; import io.github.dsheirer.source.tuner.ITunerErrorListener; import io.github.dsheirer.source.tuner.Tuner; import io.github.dsheirer.source.tuner.TunerClass; -import io.github.dsheirer.source.tuner.airspy.AirspyTunerController.BoardID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -71,7 +70,7 @@ public class AirspyTuner extends Tuner mLog.error("error getting serial number", e); } - return BoardID.AIRSPY.getLabel(); + return "Airspy"; } @Override diff --git a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerController.java index b9abf805..b43edfe3 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerController.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2025 Dennis Sheirer + * Copyright (C) 2014-2026 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,31 +19,40 @@ package io.github.dsheirer.source.tuner.airspy; -import io.github.dsheirer.buffer.INativeBufferFactory; -import io.github.dsheirer.buffer.sample.SampleNativeBufferFactory; +import io.github.dsheirer.airspy.Airspy; +import io.github.dsheirer.airspy.AirspyDevice; +import io.github.dsheirer.airspy.AirspyException; +import io.github.dsheirer.airspy.SampleBuffer; +import io.github.dsheirer.airspy.SampleType; +import io.github.dsheirer.buffer.FloatNativeBuffer; +import io.github.dsheirer.buffer.INativeBuffer; +import io.github.dsheirer.sample.Listener; import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.tuner.ITunerErrorListener; +import io.github.dsheirer.source.tuner.TunerController; import io.github.dsheirer.source.tuner.TunerType; import io.github.dsheirer.source.tuner.configuration.TunerConfiguration; -import io.github.dsheirer.source.tuner.usb.USBTunerController; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; +import io.github.dsheirer.source.tuner.manager.TunerManager; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; -import org.apache.commons.io.EndianUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.usb4java.Context; +import org.usb4java.Device; +import org.usb4java.DeviceDescriptor; +import org.usb4java.DeviceHandle; +import org.usb4java.DeviceList; import org.usb4java.LibUsb; -import org.usb4java.LibUsbException; - -import javax.usb.UsbException; /** - * Airspy tuner controller + * Airspy tuner controller, implemented with the jAirspy library (libairspy). + * + * The tuner is discovered with LibUsb at a USB bus and port, like all other USB tuners, however libairspy owns the + * USB device, its transfers, and the conversion of real samples to complex samples. */ -public class AirspyTunerController extends USBTunerController +public class AirspyTunerController extends TunerController { private final static Logger mLog = LoggerFactory.getLogger(AirspyTunerController.class); public static final Gain LINEARITY_GAIN_DEFAULT = Gain.LINEARITY_14; @@ -63,16 +72,21 @@ public class AirspyTunerController extends USBTunerController public static final long MAXIMUM_TUNABLE_FREQUENCY_HZ = 1800000000l; public static final long FREQUENCY_DEFAULT = 101100000; public static final double USABLE_BANDWIDTH_PERCENT = 0.90; - private static final long USB_TIMEOUT_MS = 2000l; //milliseconds - private static final int USB_TRANSFER_BUFFER_SIZE = 262144; - private static final byte USB_REQUEST_IN = (byte) (LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE); - private static final byte USB_REQUEST_OUT = (byte) (LibUsb.ENDPOINT_OUT | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE); public static final DecimalFormat MHZ_FORMATTER = new DecimalFormat("#.00 MHz"); public static final AirspySampleRate DEFAULT_SAMPLE_RATE = new AirspySampleRate(0, 10000000, "10.00 MHz"); - private SampleNativeBufferFactory mNativeBufferFactory = new SampleNativeBufferFactory(); + //libairspy uses 262,144 byte (unpacked) transfers of 16-bit real samples, which it converts to 65,536 complex samples + private static final int BUFFER_SAMPLE_COUNT = 65536; + //Complex samples from libairspy are half the amplitude (-6 dB) of the analytic signal for a real input. Restoring + //this keeps spectrum levels and squelch thresholds consistent with the previous Java sample converter. + private static final float SAMPLE_SCALE = 2.0f; + private final int mBus; + private final String mPortAddress; + private AirspyDevice mDevice; private AirspyDeviceInformation mDeviceInfo; private List mSampleRates = new ArrayList<>(); private int mSampleRate = 0; + private volatile float mSamplesPerMillisecond = 0.0f; + private boolean mStreaming = false; /** * Constructs an instance @@ -82,7 +96,13 @@ public class AirspyTunerController extends USBTunerController */ public AirspyTunerController(int bus, String portAddress, ITunerErrorListener tunerErrorListener) { - super(bus, portAddress, MINIMUM_TUNABLE_FREQUENCY_HZ, MAXIMUM_TUNABLE_FREQUENCY_HZ, 0, USABLE_BANDWIDTH_PERCENT, tunerErrorListener); + super(tunerErrorListener); + mBus = bus; + mPortAddress = portAddress; + setMinimumFrequency(MINIMUM_TUNABLE_FREQUENCY_HZ); + setMaximumFrequency(MAXIMUM_TUNABLE_FREQUENCY_HZ); + setMiddleUnusableHalfBandwidth(0); + setUsableBandwidthPercentage(USABLE_BANDWIDTH_PERCENT); } @Override @@ -91,77 +111,333 @@ public class AirspyTunerController extends USBTunerController return TunerType.AIRSPY_R820T; } - @Override - protected INativeBufferFactory getNativeBufferFactory() - { - return mNativeBufferFactory; - } - - @Override - protected int getTransferBufferSize() - { - return USB_TRANSFER_BUFFER_SIZE; - } - @Override public int getBufferSampleCount() { - //Each airspy complex sample is 4 bytes, so sample count is buffer size / 4 - return USB_TRANSFER_BUFFER_SIZE / 4; + return BUFFER_SAMPLE_COUNT; } @Override - protected void deviceStart() throws SourceException + public void start() throws SourceException { - try - { - setSamplePacking(false); - } - catch(LibUsbException | UsbException | UnsupportedOperationException e) - { - mLog.info("Sample packing is not supported by airspy firmware"); - } + long serialNumber = readSerialNumber(); + + getLock().lock(); try { - setReceiverMode(true); - } - catch(Exception e) - { - mLog.error("Couldn't enable airspy receiver mode", e); - } + mDevice = Airspy.open(serialNumber); - setFrequency(FREQUENCY_DEFAULT); + //Sample packing is off by default in libairspy and the sample type must be set before reading sample + //rates, since libairspy reports real sample rates as twice the complex sample rates. + mDevice.setSampleType(SampleType.FLOAT32_IQ); + mDeviceInfo = new AirspyDeviceInformation(mDevice.boardName(), mDevice.firmwareVersion(), + mDevice.partIdSerialNumber()); + mSampleRates.clear(); - try - { - determineAvailableSampleRates(); - } - catch(LibUsbException | UsbException e) - { - mLog.error("Error identifying available samples rates", e); - } + for(long rate : mDevice.sampleRates()) + { + mSampleRates.add(new AirspySampleRate(mSampleRates.size(), (int)rate, formatSampleRate((int)rate))); + } - try - { + if(mSampleRates.isEmpty()) + { + mSampleRates.add(DEFAULT_SAMPLE_RATE); + } + + setFrequency(FREQUENCY_DEFAULT); setSampleRate(mSampleRates.get(0)); } - catch(IllegalArgumentException | LibUsbException | UsbException e) + catch(AirspyException | SourceException | LinkageError e) { - mLog.error("Setting sample rate is not supported by firmware", e); + closeDevice(mDevice); + mDevice = null; + throw new SourceException("Can't start Airspy tuner - " + e.getMessage(), e); + } + finally + { + getLock().unlock(); } } @Override - protected void deviceStop() + public void stop() + { + AirspyDevice device; + + getLock().lock(); + + try + { + device = mDevice; + mDevice = null; + mStreaming = false; + } + finally + { + getLock().unlock(); + } + + mNativeBufferBroadcaster.clear(); + closeDevice(device); + } + + /** + * Closes the device, which also stops streaming. + * @param device to close, or null + */ + private static void closeDevice(AirspyDevice device) + { + if(device != null) + { + try + { + device.close(); + } + catch(AirspyException | RuntimeException e) + { + mLog.error("Error closing Airspy device", e); + } + } + } + + /** + * Reads the serial number of the device at this controller's USB bus and port, since libairspy opens a specific + * device by serial number. The device is closed again before returning so that libairspy can open it. + * @return serial number + * @throws SourceException if the device isn't found or can't be accessed. + */ + private long readSerialNumber() throws SourceException + { + Context context = new Context(); + int status = LibUsb.init(context); + + if(status != LibUsb.SUCCESS) + { + throw new SourceException("Can't initialize libusb library - " + LibUsb.errorName(status)); + } + + try + { + DeviceList deviceList = new DeviceList(); + int count = LibUsb.getDeviceList(context, deviceList); + + if(count < 0) + { + throw new SourceException("Can't get the USB device list - " + LibUsb.errorName(count)); + } + + try + { + for(Device device : deviceList) + { + if(LibUsb.getBusNumber(device) == mBus && LibUsb.getPortNumber(device) > 0 && + mPortAddress != null && mPortAddress.equals(TunerManager.getPortAddress(device))) + { + return readSerialNumber(device); + } + } + } + finally + { + LibUsb.freeDeviceList(deviceList, true); + } + } + finally + { + LibUsb.exit(context); + } + + throw new SourceException("Couldn't find USB device at bus [" + mBus + "] port [" + mPortAddress + "]"); + } + + /** + * Reads the serial number from the device's USB serial string, formatted as: AIRSPY SN:0123456789ABCDEF + * @param device to read + * @return serial number + * @throws SourceException if the device can't be opened or the serial string isn't valid. + */ + private static long readSerialNumber(Device device) throws SourceException + { + DeviceDescriptor descriptor = new DeviceDescriptor(); + int status = LibUsb.getDeviceDescriptor(device, descriptor); + + if(status != LibUsb.SUCCESS) + { + throw new SourceException("Can't obtain tuner's device descriptor - " + LibUsb.errorName(status)); + } + + DeviceHandle handle = new DeviceHandle(); + status = LibUsb.open(device, handle); + + if(status == LibUsb.ERROR_ACCESS) + { + mLog.error("Access to USB tuner denied - (windows) reinstall zadig driver or (linux) blacklist driver and/or check udev rules"); + throw new SourceException("access denied - if using linux, blacklist the default driver and/or install udev rules"); + } + else if(status != LibUsb.SUCCESS) + { + mLog.error("Can't open USB tuner - check driver or Linux udev rules"); + throw new SourceException("Can't open USB tuner - reinstall driver? - " + LibUsb.errorName(status)); + } + + String serial; + + try + { + serial = LibUsb.getStringDescriptor(handle, descriptor.iSerialNumber()); + } + finally + { + LibUsb.close(handle); + } + + try + { + return Long.parseUnsignedLong(serial.substring(serial.lastIndexOf(':') + 1), 16); + } + catch(NullPointerException | NumberFormatException e) + { + throw new SourceException("Can't read Airspy serial number from USB serial string [" + serial + "]", e); + } + } + + /** + * Operation that requires the device. + */ + private interface DeviceOperation + { + void execute(AirspyDevice device) throws AirspyException; + } + + /** + * Executes the operation under the controller lock so that the device can't be closed while it is in use. The + * operation is ignored when the device isn't open, which allows for calls that arrive during tuner shutdown. + * @param operation to execute + * @throws SourceException if the operation fails + */ + private void execute(DeviceOperation operation) throws SourceException + { + getLock().lock(); + + try + { + if(mDevice != null) + { + operation.execute(mDevice); + } + } + catch(AirspyException e) + { + throw new SourceException(e.getMessage(), e); + } + finally + { + getLock().unlock(); + } + } + + /** + * Receives converted samples on the libairspy thread and dispatches them to the buffer listeners. Exceptions + * are contained here since an exception reaching the library would stop the sample stream. + * @param buffer from the library, which is only valid until this method returns. + * @return true to continue streaming + */ + private boolean receive(SampleBuffer buffer) { try { - setReceiverMode(false); + float[] samples = buffer.copyFloats(); + + for(int x = 0; x < samples.length; x++) + { + samples[x] *= SAMPLE_SCALE; + } + + broadcast(new FloatNativeBuffer(samples, System.currentTimeMillis(), mSamplesPerMillisecond)); } - catch(Exception e) + catch(RuntimeException e) { - mLog.error("Couldn't enable airspy receiver mode", e); + mLog.error("Error dispatching Airspy sample buffer", e); + } + + return true; + } + + /** + * Starts the sample stream if it's not already started. + */ + private void startStreaming() throws SourceException + { + if(!mStreaming) + { + execute(device -> device.startRx(this::receive)); + mStreaming = mDevice != null; + } + } + + /** + * Stops the sample stream if it's started. + */ + private void stopStreaming() throws SourceException + { + if(mStreaming) + { + mStreaming = false; + execute(AirspyDevice::stopRx); + } + } + + /** + * Adds the IQ buffer listener and automatically starts streaming, if not already started. + */ + @Override + public void addBufferListener(Listener listener) + { + getLock().lock(); + + try + { + if(mDevice != null) + { + super.addBufferListener(listener); + startStreaming(); + } + } + catch(SourceException se) + { + mLog.error("Error starting streaming on Airspy tuner", se); + } + finally + { + getLock().unlock(); + } + } + + /** + * Removes the IQ buffer listener and stops streaming if there are no more listeners. + */ + @Override + public void removeBufferListener(Listener listener) + { + getLock().lock(); + + try + { + super.removeBufferListener(listener); + + if(!hasBufferListeners()) + { + stopStreaming(); + } + } + catch(SourceException se) + { + mLog.error("Error stopping streaming on Airspy tuner", se); + } + finally + { + getLock().unlock(); } } @@ -173,29 +449,8 @@ public class AirspyTunerController extends USBTunerController if(tunerConfiguration instanceof AirspyTunerConfiguration config) { - int sampleRate = config.getSampleRate(); - AirspySampleRate rate = getSampleRate(sampleRate); - - if(rate == null) - { - if(!mSampleRates.isEmpty()) - { - rate = mSampleRates.get(0); - } - else - { - rate = DEFAULT_SAMPLE_RATE; - } - } - - try - { - setSampleRate(rate); - } - catch(UsbException e) - { - throw new SourceException("Couldn't set sample rate [" + rate.toString() + "]", e); - } + AirspySampleRate rate = getSampleRate(config.getSampleRate()); + setSampleRate(rate != null ? rate : mSampleRates.isEmpty() ? DEFAULT_SAMPLE_RATE : mSampleRates.get(0)); try { @@ -230,24 +485,11 @@ public class AirspyTunerController extends USBTunerController } @Override - public synchronized void setTunedFrequency(long frequency) throws SourceException + public void setTunedFrequency(long frequency) throws SourceException { if(MINIMUM_TUNABLE_FREQUENCY_HZ <= frequency && frequency <= MAXIMUM_TUNABLE_FREQUENCY_HZ) { - ByteBuffer buffer = ByteBuffer.allocateDirect(4); - buffer.order(ByteOrder.LITTLE_ENDIAN); - buffer.putInt((int) frequency); - buffer.rewind(); - - try - { - write(Command.SET_FREQUENCY, 0, 0, buffer); - } - catch(UsbException e) - { - mLog.error("error setting frequency [" + frequency + "]", e); - throw new SourceException("error setting frequency [" + frequency + "]", e); - } + execute(device -> device.setFrequency(frequency)); } else { @@ -262,49 +504,63 @@ public class AirspyTunerController extends USBTunerController } /** - * Sets the sample rate to the rate specified by the index value in the available sample rates map + * Sets the sample rate. The sample stream is stopped for the change, because libairspy clears the halt on the + * streaming USB endpoint when it changes the sample rate. * - * @param rate to a sample rate in the available samples rates map. - * @throws IllegalArgumentException if index is not a valid rate index - * @throws LibUsbException if there was a read error or if this operation is not supported by the current firmware - * @throws UsbException if there was a USB error + * @param rate to set + * @throws SourceException if the rate can't be set */ - public void setSampleRate(AirspySampleRate rate) throws LibUsbException, UsbException, SourceException + public void setSampleRate(AirspySampleRate rate) throws SourceException { - getNativeBufferFactory().setSamplesPerMillisecond((float)rate.getRate() / 1000.0f); - if(rate.getRate() != mSampleRate) + getLock().lock(); + + try { - int result = readByte(Command.SET_SAMPLE_RATE, 0, rate.getIndex(), true); + mSamplesPerMillisecond = (float)rate.getRate() / 1000.0f; - if(result != 1) + if(rate.getRate() != mSampleRate) { - throw new UsbException("Error setting sample rate [" + rate + "] rate - return value [" + result + "]"); - } - else - { - mSampleRate = rate.getRate(); - mFrequencyController.setSampleRate(mSampleRate); + boolean streaming = mStreaming; + stopStreaming(); - // Update the Usable Bandwidth Percentage, as the usable range varies with the sample rate. - // Values below are from https://github.com/DSheirer/sdrtrunk/issues/815#issuecomment-699859590 - switch(mSampleRate) + try { - default: - case 10000000: - super.setUsableBandwidthPercentage(0.90); - break; - case 6000000: - super.setUsableBandwidthPercentage(0.83); - break; - case 3000000: - super.setUsableBandwidthPercentage(0.66); - break; - case 2500000: - super.setUsableBandwidthPercentage(0.60); - break; + execute(device -> device.setSampleRate(rate.getRate())); + mSampleRate = rate.getRate(); + mFrequencyController.setSampleRate(mSampleRate); + + // Update the Usable Bandwidth Percentage, as the usable range varies with the sample rate. + // Values below are from https://github.com/DSheirer/sdrtrunk/issues/815#issuecomment-699859590 + switch(mSampleRate) + { + default: + case 10000000: + super.setUsableBandwidthPercentage(0.90); + break; + case 6000000: + super.setUsableBandwidthPercentage(0.83); + break; + case 3000000: + super.setUsableBandwidthPercentage(0.66); + break; + case 2500000: + super.setUsableBandwidthPercentage(0.60); + break; + } + } + finally + { + if(streaming) + { + startStreaming(); + } } } } + finally + { + getLock().unlock(); + } } /** @@ -342,62 +598,29 @@ public class AirspyTunerController extends USBTunerController return null; } - /** - * Enables/Disables sample packing to allow two 12-bit samples to be packed into 3 bytes (enabled) or 4 bytes (disabled). - * - * @param enabled true or false - * @throws UsbException if sample packing is not supported by the current device firmware or if there were usb - * communication issues - */ - public void setSamplePacking(boolean enabled) throws LibUsbException, UsbException - { - int result = readByte(Command.SET_PACKING, 0, (enabled ? 1 : 0), true); - - if(result != 1) - { - throw new UsbException("Couldnt set sample packing enabled: " + enabled); - } - - /* If we didn't throw an exception above, then update the sample adapter - * to process samples accordingly */ - mNativeBufferFactory.setSamplePacking(enabled); - } - /** * Enables/disables the mixer automatic gain setting * - * @param enabled - * @throws LibUsbException on unsuccessful read operation - * @throws UsbException on USB error + * @param enabled true to enable + * @throws SourceException on error */ - public synchronized void setMixerAGC(boolean enabled) throws LibUsbException, UsbException + public void setMixerAGC(boolean enabled) throws SourceException { - int result = readByte(Command.SET_MIXER_AGC, 0, (enabled ? 1 : 0), true); - - if(result != LibUsb.SUCCESS) - { - throw new UsbException("Couldnt set mixer AGC enabled: " + enabled); - } + execute(device -> device.setMixerAgc(enabled)); } /** * Enables/disables the low noise amplifier automatic gain setting * - * @param enabled - * @throws LibUsbException on unsuccessful read operation - * @throws UsbException on USB error + * @param enabled true to enable + * @throws SourceException on error */ - public synchronized void setLNAAGC(boolean enabled) throws LibUsbException, UsbException + public void setLNAAGC(boolean enabled) throws SourceException { - int result = readByte(Command.SET_LNA_AGC, 0, (enabled ? 1 : 0), true); - - if(result != LibUsb.SUCCESS) - { - throw new UsbException("Couldn't set LNA AGC enabled: " + enabled); - } + execute(device -> device.setLnaAgc(enabled)); } - public void setGain(Gain gain) throws UsbException + public void setGain(Gain gain) throws SourceException { if(gain != Gain.CUSTOM) { @@ -413,20 +636,14 @@ public class AirspyTunerController extends USBTunerController * Sets LNA gain * * @param gain - value within range of LNA_GAIN_MIN to LNA_GAIN_MAX - * @throws LibUsbException on error in java USB wrapper - * @throws UsbException on error in USB transfer + * @throws SourceException on error * @throws IllegalArgumentException if gain value is invalid */ - public synchronized void setLNAGain(int gain) throws LibUsbException, UsbException, IllegalArgumentException + public void setLNAGain(int gain) throws SourceException, IllegalArgumentException { if(LNA_GAIN_MIN <= gain && gain <= LNA_GAIN_MAX) { - int result = readByte(Command.SET_LNA_GAIN, 0, gain, true); - - if(result != LibUsb.SUCCESS) - { - throw new UsbException("Couldnt set LNA gain to: " + gain); - } + execute(device -> device.setLnaGain(gain)); } else { @@ -439,20 +656,14 @@ public class AirspyTunerController extends USBTunerController * Sets Mixer gain * * @param gain - value within range of MIXER_GAIN_MIN to MIXER_GAIN_MAX - * @throws LibUsbException on error in java USB wrapper - * @throws UsbException on error in USB transfer + * @throws SourceException on error * @throws IllegalArgumentException if gain value is invalid */ - public void setMixerGain(int gain) throws LibUsbException, UsbException, IllegalArgumentException + public void setMixerGain(int gain) throws SourceException, IllegalArgumentException { if(MIXER_GAIN_MIN <= gain && gain <= MIXER_GAIN_MAX) { - int result = readByte(Command.SET_MIXER_GAIN, 0, gain, true); - - if(result != LibUsb.SUCCESS) - { - throw new UsbException("Couldnt set mixer gain to: " + gain); - } + execute(device -> device.setMixerGain(gain)); } else { @@ -464,21 +675,15 @@ public class AirspyTunerController extends USBTunerController /** * Sets IF (VGA) gain * - * @param gain - value within range of VGA_GAIN_MIN to VGA_GAIN_MAX - * @throws LibUsbException on error in java USB wrapper - * @throws UsbException on error in USB transfer + * @param gain - value within range of IF_GAIN_MIN to IF_GAIN_MAX + * @throws SourceException on error * @throws IllegalArgumentException if gain value is invalid */ - public void setIFGain(int gain) throws LibUsbException, UsbException, IllegalArgumentException + public void setIFGain(int gain) throws SourceException, IllegalArgumentException { if(IF_GAIN_MIN <= gain && gain <= IF_GAIN_MAX) { - int result = readByte(Command.SET_VGA_GAIN, 0, gain, true); - - if(result != LibUsb.SUCCESS) - { - throw new UsbException("Couldnt set VGA gain to: " + gain); - } + execute(device -> device.setVgaGain(gain)); } else { @@ -487,53 +692,6 @@ public class AirspyTunerController extends USBTunerController } } - public void setReceiverMode(boolean enabled) throws LibUsbException, UsbException - { - //Empty buffer to throw away - ByteBuffer buffer = ByteBuffer.allocateDirect(0); - write(Command.RECEIVER_MODE, (enabled ? 1 : 0), 0, buffer); - } - - /** - * Queries the device for available sample rates. Will always provide at - * least the default 10 MHz sample rate. - */ - private void determineAvailableSampleRates() throws LibUsbException, UsbException - { - mSampleRates.clear(); - - //Get a count of available sample rates. If we get an exception, then - //we're using an older firmware revision and only the default 10 MHz - //rate is supported - try - { - byte[] rawCount = readArray(Command.GET_SAMPLE_RATES, 0, 0, 4); - - if(rawCount != null) - { - int count = EndianUtils.readSwappedInteger(rawCount, 0); - - byte[] rawRates = readArray(Command.GET_SAMPLE_RATES, 0, - count, (count * 4)); - - for(int x = 0; x < count; x++) - { - int rate = EndianUtils.readSwappedInteger(rawRates, (x * 4)); - mSampleRates.add(new AirspySampleRate(x, rate, formatSampleRate(rate))); - } - } - } - catch(LibUsbException e) - { - //Press on, nothing else to do here .. - } - - if(mSampleRates.isEmpty()) - { - mSampleRates.add(DEFAULT_SAMPLE_RATE); - } - } - /** * Formats the rate in hertz for display as megahertz */ @@ -543,166 +701,13 @@ public class AirspyTunerController extends USBTunerController } /** - * Device information + * Device information, available once the tuner has been started. */ public AirspyDeviceInformation getDeviceInfo() { - //Lazy initialization - if(mDeviceInfo == null) - { - readDeviceInfo(); - } - return mDeviceInfo; } - /** - * Reads version information from the device and populates the info object - */ - private void readDeviceInfo() - { - if(mDeviceInfo == null) - { - mDeviceInfo = new AirspyDeviceInformation(); - } - - /* Board ID */ - try - { - int boardID = readByte(Command.BOARD_ID_READ, 0, 0, true); - mDeviceInfo.setBoardID(boardID); - } - catch(LibUsbException | UsbException e) - { - mLog.error("Error reading airspy board ID", e); - } - - /* Version String */ - try - { - //NOTE: libairspy is internally reading 127 bytes, however airspy_info script is telling it to read - //255 bytes ... things that make you go hmmmm - byte[] version = readArray(Command.VERSION_STRING_READ, 0, 0, 127); - mDeviceInfo.setVersion(version); - } - catch(LibUsbException | UsbException e) - { - mLog.error("Error reading airspy version string", e); - } - - /* Part ID and Serial Number */ - try - { - //Read 6 x 32-bit integers = 24 bytes - byte[] serial = readArray(Command.BOARD_PART_ID_SERIAL_NUMBER_READ, 0, 0, 24); - mDeviceInfo.setPartAndSerialNumber(serial); - } - catch(LibUsbException | UsbException e) - { - mLog.error("Error reading airspy version string", e); - } - } - - /** - * Reads a single byte value from the device. - * - * @param command - airspy command - * @param value - value field for usb setup packet - * @param index - index field for usb setup packet - * @return - byte value as an integer - * @throws LibUsbException if the operation is unsuccesful - * @throws UsbException on any usb errors - */ - private int readByte(Command command, int value, int index, boolean signed) throws LibUsbException, UsbException - { - if(isRunning()) - { - ByteBuffer buffer = ByteBuffer.allocateDirect(1); - - int transferred = LibUsb.controlTransfer(getDeviceHandle(), USB_REQUEST_IN, command.getValue(), - (short) value, (short) index, buffer, USB_TIMEOUT_MS); - - if(transferred < 0) - { - throw new LibUsbException("read error", transferred); - } - - byte result = buffer.get(0); - - if(signed) - { - return (result & 0xFF); - } - else - { - return result; - } - } - - return 0; - } - - /** - * Reads a multi-byte value from the device - * - * @param command - airspy command - * @param value - usb packet value - * @param index - usb packet index - * @param length - number of bytes to read - * @return - bytes read from the device - * @throws LibUsbException if quantity of bytes read doesn't equal the requested number of bytes - * @throws UsbException on error communicating with the device - */ - private byte[] readArray(Command command, int value, int index, int length) throws LibUsbException, UsbException - { - if(isRunning()) - { - ByteBuffer buffer = ByteBuffer.allocateDirect(length); - - int transferred = LibUsb.controlTransfer(getDeviceHandle(), USB_REQUEST_IN, command.getValue(), (short) value, - (short) index, buffer, USB_TIMEOUT_MS); - - if(transferred < 0) - { - throw new LibUsbException("read error", transferred); - } - - byte[] results = new byte[transferred]; - buffer.get(results); - return results; - } - - return new byte[0]; - } - - /** - * Writes the buffer contents to the device - * - * @param command - airspy command - * @param value - usb packet value - * @param index - usb packet index - * @param buffer - data to write to the device - * @throws UsbException on error - */ - public void write(Command command, int value, int index, ByteBuffer buffer) throws UsbException - { - if(isRunning()) - { - int transferred = LibUsb.controlTransfer(getDeviceHandle(), USB_REQUEST_OUT, command.getValue(), - (short) value, (short) index, buffer, USB_TIMEOUT_MS); - - if(transferred < 0) - { - throw new LibUsbException("error writing byte buffer", transferred); - } - else if(transferred != buffer.capacity()) - { - throw new LibUsbException("transferred bytes [" + transferred + "] is not what was expected [" + - buffer.capacity() + "]", transferred); - } - } - } - public enum GainMode { LINEARITY, @@ -845,194 +850,4 @@ public class AirspyTunerController extends USBTunerController return EnumSet.range(SENSITIVITY_1, SENSITIVITY_22); } } - - /** - * Airspy Board Identifier - */ - public enum BoardID - { - AIRSPY(0, "Airspy"), - UNKNOWN(-1, "Unknown"); - - private int mValue; - private String mLabel; - - private BoardID(int value, String label) - { - mValue = value; - mLabel = label; - } - - public int getValue() - { - return mValue; - } - - public String getLabel() - { - return mLabel; - } - - public static BoardID fromValue(int value) - { - if(value == 0) - { - return AIRSPY; - } - - return UNKNOWN; - } - } - - /** - * Airspy Commands - */ - public enum Command - { - INVALID(0), - RECEIVER_MODE(1), - SI5351C_WRITE(2), - SI5351C_READ(3), - R820T_WRITE(4), - R820T_READ(5), - SPIFLASH_ERASE(6), - SPIFLASH_WRITE(7), - SPIFLASH_READ(8), - BOARD_ID_READ(9), - VERSION_STRING_READ(10), - BOARD_PART_ID_SERIAL_NUMBER_READ(11), - SET_SAMPLE_RATE(12), - SET_FREQUENCY(13), - SET_LNA_GAIN(14), - SET_MIXER_GAIN(15), - SET_VGA_GAIN(16), - SET_LNA_AGC(17), - SET_MIXER_AGC(18), - MS_VENDOR_COMMAND(19), - SET_RF_BIAS_COMMAND(20), - GPIO_WRITE(21), - GPIO_READ(22), - GPIO_DIR__WRITE(23), - GPIO_DIR_READ(24), - GET_SAMPLE_RATES(25), - SET_PACKING(26); - - private int mValue; - - Command(int value) - { - mValue = value; - } - - public byte getValue() - { - return (byte) mValue; - } - - public static Command fromValue(int value) - { - if(0 <= value && value <= 25) - { - return Command.values()[value]; - } - - return INVALID; - } - } - - public enum ReceiverMode - { - OFF(0), - ON(1); - - private int mValue; - - private ReceiverMode(int value) - { - mValue = value; - } - - public int getValue() - { - return mValue; - } - } - - /** - * General Purpose Input/Output Ports (accessible on the airspy board) - */ - public enum GPIOPort - { - PORT_0(0), - PORT_1(1), - PORT_2(2), - PORT_3(3), - PORT_4(4), - PORT_5(5), - PORT_6(6), - PORT_7(7); - - private int mValue; - - private GPIOPort(int value) - { - mValue = value; - } - - public int getValue() - { - return mValue; - } - } - - /** - * General Purpose Input/Output Pins (accessible on the airspy board) - */ - public enum GPIOPin - { - PIN_0(0), - PIN_1(1), - PIN_2(2), - PIN_3(3), - PIN_4(4), - PIN_5(5), - PIN_6(6), - PIN_7(7), - PIN_8(8), - PIN_9(9), - PIN_10(10), - PIN_11(11), - PIN_12(12), - PIN_13(13), - PIN_14(14), - PIN_15(15), - PIN_16(16), - PIN_17(17), - PIN_18(18), - PIN_19(19), - PIN_20(20), - PIN_21(21), - PIN_22(22), - PIN_23(23), - PIN_24(24), - PIN_25(25), - PIN_26(26), - PIN_27(27), - PIN_28(28), - PIN_29(29), - PIN_30(30), - PIN_31(31); - - private int mValue; - - GPIOPin(int value) - { - mValue = value; - } - - public int getValue() - { - return mValue; - } - } -} \ No newline at end of file +} diff --git a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerEditor.java b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerEditor.java index ed32e804..fc684e3b 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerEditor.java +++ b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerEditor.java @@ -689,8 +689,8 @@ public class AirspyTunerEditor extends TunerEditor"); - sb.append("Board ID: "); - sb.append(info.getBoardID().getLabel()); + sb.append("Board: "); + sb.append(info.getBoardName()); sb.append("
"); return sb.toString(); diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java index dabd85e9..2a76d59a 100644 --- a/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java +++ b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java @@ -39,9 +39,6 @@ import io.github.dsheirer.vector.calibrate.magnitude.MagnitudeCalibration; import io.github.dsheirer.vector.calibrate.mixer.ComplexMixerCalibration; import io.github.dsheirer.vector.calibrate.oscillator.ComplexOscillatorCalibration; import io.github.dsheirer.vector.calibrate.oscillator.RealOscillatorCalibration; -import io.github.dsheirer.vector.calibrate.sample.PackedSampleConverterCalibration; -import io.github.dsheirer.vector.calibrate.sample.UnpackedInterleavedSampleConverterCalibration; -import io.github.dsheirer.vector.calibrate.sample.UnpackedSampleConverterCalibration; import io.github.dsheirer.vector.calibrate.sync.DMRSoftSyncCalibration; import io.github.dsheirer.vector.calibrate.sync.NXDNSoftSyncCalibration; import io.github.dsheirer.vector.calibrate.sync.P25P1SoftSyncCalibration; @@ -103,9 +100,6 @@ public class CalibrationManager sInstance = new CalibrationManager(); - sInstance.add(new PackedSampleConverterCalibration()); - sInstance.add(new UnpackedSampleConverterCalibration()); - sInstance.add(new UnpackedInterleavedSampleConverterCalibration()); sInstance.add(new AmDemodulatorCalibration()); sInstance.add(new ChannelDFTCalibration()); sInstance.add(new ComplexGainCalibration()); diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationType.java b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationType.java index f8f97192..94959763 100644 --- a/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationType.java +++ b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationType.java @@ -53,9 +53,6 @@ public enum CalibrationType OSCILLATOR_REAL("Real Oscillator", 1), P25P1_SOFT_SYNC_DETECTOR("P25 Phase 1 Soft Sync Detector", 2), P25P2_SOFT_SYNC_DETECTOR("P25 Phase 2 Soft Sync Detector", 1), - SAMPLE_PACKED_CONVERTER("Packed Sample Converter", 2), - SAMPLE_UNPACKED_INTERLEAVED_ITERATOR("Unpacked Interleaved Sample Iterator", 2), - SAMPLE_UNPACKED_ITERATOR("Unpacked Sample Iterator", 2), SQUELCHING_FM_DEMODULATOR("Squelching FM Demodulator", 1), WINDOW("Window", 1); diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/sample/PackedSampleConverterCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/sample/PackedSampleConverterCalibration.java deleted file mode 100644 index 987dad35..00000000 --- a/src/main/java/io/github/dsheirer/vector/calibrate/sample/PackedSampleConverterCalibration.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2026 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.vector.calibrate.sample; - -import io.github.dsheirer.buffer.sample.ScalarUnpackedSampleConverter; -import io.github.dsheirer.buffer.sample.VectorUnpackedSampleConverter; -import io.github.dsheirer.vector.calibrate.Calibration; -import io.github.dsheirer.vector.calibrate.CalibrationException; -import io.github.dsheirer.vector.calibrate.CalibrationType; -import io.github.dsheirer.vector.calibrate.Implementation; -import java.nio.ByteBuffer; -import org.apache.commons.math3.stat.descriptive.moment.Mean; - -public class PackedSampleConverterCalibration extends Calibration -{ - private static final int BUFFER_SIZE = 262144; - private static final int ITERATION_DURATION_MS = 1000; - private static final int WARM_UP_ITERATIONS = 5; - private static final int TEST_ITERATIONS = 5; - - /** - * Constructs an instance - */ - public PackedSampleConverterCalibration() - { - super(CalibrationType.SAMPLE_PACKED_CONVERTER); - } - - @Override - public void calibrate() throws CalibrationException - { - Mean scalarMean = new Mean(); - Mean vectorMean = new Mean(); - - byte[] samples = new byte[BUFFER_SIZE]; - ByteBuffer buffer = ByteBuffer.wrap(samples); - - for(int scalarTest = 0; scalarTest < WARM_UP_ITERATIONS; scalarTest++) - { - long score = testScalar(buffer); - scalarMean.increment(score); - } - - mLog.info("PACKED SAMPLE CONVERTER WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); - - for(int vectorTest = 0; vectorTest < WARM_UP_ITERATIONS; vectorTest++) - { - long score = testVector(buffer); - vectorMean.increment(score); - } - - mLog.info("PACKED SAMPLE CONVERTER WARMUP - VECTOR: " + DECIMAL_FORMAT.format(vectorMean.getResult())); - - scalarMean.clear(); - vectorMean.clear(); - - for(int scalarTest = 0; scalarTest < TEST_ITERATIONS; scalarTest++) - { - long score = testScalar(buffer); - scalarMean.increment(score); - } - - mLog.info("PACKED SAMPLE CONVERTER - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); - - for(int vectorTest = 0; vectorTest < TEST_ITERATIONS; vectorTest++) - { - long score = testVector(buffer); - vectorMean.increment(score); - } - - mLog.info("PACKED SAMPLE CONVERTER - VECTOR: " + DECIMAL_FORMAT.format(vectorMean.getResult())); - - if(scalarMean.getResult() > vectorMean.getResult()) - { - setImplementation(Implementation.SCALAR); - } - else - { - setImplementation(Implementation.VECTOR_SIMD_PREFERRED); - } - - mLog.info("PACKED SAMPLE CONVERTER - SETTING OPTIMAL IMPLEMENTATION TO: " + getImplementation()); - } - - private long testScalar(ByteBuffer buffer) - { - long start = System.currentTimeMillis(); - - ScalarUnpackedSampleConverter scalor = new ScalarUnpackedSampleConverter(); - - long count = 0; - double accumulator = 0.0; - - while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) - { - short[] converted = scalor.convert(buffer); - accumulator += converted[2]; - count++; - } - - return count + (long)(accumulator * 0); - } - - private long testVector(ByteBuffer buffer) - { - VectorUnpackedSampleConverter vector = new VectorUnpackedSampleConverter(); - long start = System.currentTimeMillis(); - double accumulator = 0.0; - long count = 0; - - while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) - { - short[] converted = vector.convert(buffer); - accumulator += converted[2]; - count++; - } - - return count + (long)(accumulator * 0); - } -} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/sample/UnpackedInterleavedSampleConverterCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/sample/UnpackedInterleavedSampleConverterCalibration.java deleted file mode 100644 index 0cc2f947..00000000 --- a/src/main/java/io/github/dsheirer/vector/calibrate/sample/UnpackedInterleavedSampleConverterCalibration.java +++ /dev/null @@ -1,324 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2026 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.vector.calibrate.sample; - -import io.github.dsheirer.buffer.sample.SampleBufferIterator; -import io.github.dsheirer.buffer.sample.SampleInterleavedBufferIteratorScalar; -import io.github.dsheirer.buffer.sample.SampleInterleavedBufferIteratorVector128Bits; -import io.github.dsheirer.buffer.sample.SampleInterleavedBufferIteratorVector256Bits; -import io.github.dsheirer.buffer.sample.SampleInterleavedBufferIteratorVector512Bits; -import io.github.dsheirer.buffer.sample.SampleInterleavedBufferIteratorVector64Bits; -import io.github.dsheirer.vector.calibrate.Calibration; -import io.github.dsheirer.vector.calibrate.CalibrationException; -import io.github.dsheirer.vector.calibrate.CalibrationType; -import io.github.dsheirer.vector.calibrate.Implementation; -import jdk.incubator.vector.FloatVector; -import org.apache.commons.math3.stat.descriptive.moment.Mean; - -/** - * Calculates optimal implementation (SCALAR vs VECTOR) for interleaved sample native buffers. - */ -public class UnpackedInterleavedSampleConverterCalibration extends Calibration -{ - private static final int BUFFER_SIZE = 131072; - private static final int ITERATION_DURATION_MS = 1000; - private static final int WARMUP_ITERATIONS = 5; - private static final int TEST_ITERATIONS = 5; - - /** - * Constructs an instance - */ - public UnpackedInterleavedSampleConverterCalibration() - { - super(CalibrationType.SAMPLE_UNPACKED_INTERLEAVED_ITERATOR); - } - - @Override - public void calibrate() throws CalibrationException - { - short[] samples = getShortSamples(BUFFER_SIZE); - short[] residualI = getShortSamples(SampleBufferIterator.I_OVERLAP); - short[] residualQ = getShortSamples(SampleBufferIterator.Q_OVERLAP); - - //Warm-Up Phase .... - mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER - VECTOR SIMD LANES PREFERRED: " + FloatVector.SPECIES_PREFERRED.length()); - - Mean scalarMean = new Mean(); - - for(int x = 0; x < WARMUP_ITERATIONS; x++) - { - long score = calibrateScalar(samples, residualI, residualQ); - scalarMean.increment(score); - } - - mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); - - switch(FloatVector.SPECIES_PREFERRED.length()) - { - //Deliberate fall-through of each case statement so that we can test the largest - //SIMD lane width supported by hardware down to the smallest SIMD lane width. - case 16: - { - Mean vectorMean = new Mean(); - - for(int x = 0; x < WARMUP_ITERATIONS; x++) - { - long score = calibrateVector512(samples, residualI, residualQ); - vectorMean.increment(score); - } - - mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vectorMean.getResult())); - } - case 8: - { - Mean vectorMean = new Mean(); - - for(int x = 0; x < WARMUP_ITERATIONS; x++) - { - long score = calibrateVector256(samples, residualI, residualQ); - vectorMean.increment(score); - } - - mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vectorMean.getResult())); - } - case 4: - { - Mean vectorMean = new Mean(); - - for(int x = 0; x < WARMUP_ITERATIONS; x++) - { - long score = calibrateVector128(samples, residualI, residualQ); - vectorMean.increment(score); - } - - mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vectorMean.getResult())); - } - case 2: - { - Mean vectorMean = new Mean(); - - for(int x = 0; x < WARMUP_ITERATIONS; x++) - { - long score = calibrateVector64(samples, residualI, residualQ); - vectorMean.increment(score); - } - - mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vectorMean.getResult())); - } - - //Test Phase .... - scalarMean.clear(); - for(int x = 0; x < TEST_ITERATIONS; x++) - { - long score = calibrateScalar(samples, residualI, residualQ); - scalarMean.increment(score); - } - - double bestScore = scalarMean.getResult(); - setImplementation(Implementation.SCALAR); - - mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); - - switch(FloatVector.SPECIES_PREFERRED.length()) - { - //Deliberate fall-through of each case statement so that we can test the largest - //SIMD lane width supported by hardware down to the smallest SIMD lane width. - case 16: - { - Mean vectorMean = new Mean(); - - for(int x = 0; x < TEST_ITERATIONS; x++) - { - long score = calibrateVector512(samples, residualI, residualQ); - vectorMean.increment(score); - } - - if(vectorMean.getResult() > bestScore) - { - bestScore = vectorMean.getResult(); - setImplementation(Implementation.VECTOR_SIMD_512); - } - - mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER - VECTOR 512: " + DECIMAL_FORMAT.format(vectorMean.getResult())); - } - case 8: - { - Mean vectorMean = new Mean(); - - for(int x = 0; x < TEST_ITERATIONS; x++) - { - long score = calibrateVector256(samples, residualI, residualQ); - vectorMean.increment(score); - } - - if(vectorMean.getResult() > bestScore) - { - bestScore = vectorMean.getResult(); - setImplementation(Implementation.VECTOR_SIMD_256); - } - - mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER - VECTOR 256: " + DECIMAL_FORMAT.format(vectorMean.getResult())); - } - case 4: - { - Mean vectorMean = new Mean(); - - for(int x = 0; x < TEST_ITERATIONS; x++) - { - long score = calibrateVector128(samples, residualI, residualQ); - vectorMean.increment(score); - } - - if(vectorMean.getResult() > bestScore) - { - bestScore = vectorMean.getResult(); - setImplementation(Implementation.VECTOR_SIMD_128); - } - - mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER - VECTOR 128: " + DECIMAL_FORMAT.format(vectorMean.getResult())); - } - case 2: - { - Mean vectorMean = new Mean(); - - for(int x = 0; x < TEST_ITERATIONS; x++) - { - long score = calibrateVector64(samples, residualI, residualQ); - vectorMean.increment(score); - } - - if(vectorMean.getResult() > bestScore) - { - setImplementation(Implementation.VECTOR_SIMD_64); - } - - mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER - VECTOR 64: " + DECIMAL_FORMAT.format(vectorMean.getResult())); - } - } - } - - mLog.info("UNPACKED INTERLEAVED SAMPLE CONVERTER - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation()); - } - - private long calibrateScalar(short[] samples, short[] residualI, short[] residualQ) - { - long start = System.currentTimeMillis(); - double accumulator = 0.0; - long count = 0; - while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) - { - SampleInterleavedBufferIteratorScalar iterator = new SampleInterleavedBufferIteratorScalar(samples, residualI, - residualQ, 0.0f, System.currentTimeMillis(), 0.0f); - - while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)) - { - accumulator += iterator.next().samples()[2]; - count++; - } - } - - return count + (long)(accumulator * 0); - } - - private long calibrateVector64(short[] samples, short[] residualI, short[] residualQ) - { - long start = System.currentTimeMillis(); - double accumulator = 0.0; - long count = 0; - - while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) - { - SampleInterleavedBufferIteratorVector64Bits iterator = new SampleInterleavedBufferIteratorVector64Bits(samples, residualI, - residualQ, 0.0f, System.currentTimeMillis(), 0.0f); - - while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)) - { - accumulator += iterator.next().samples()[2]; - count++; - } - } - - return count + (long)(accumulator * 0); - } - - private long calibrateVector128(short[] samples, short[] residualI, short[] residualQ) - { - long start = System.currentTimeMillis(); - double accumulator = 0.0; - long count = 0; - - for(int x = 0; x < ITERATION_DURATION_MS; x++) - { - SampleInterleavedBufferIteratorVector128Bits iterator = new SampleInterleavedBufferIteratorVector128Bits(samples, residualI, - residualQ, 0.0f, System.currentTimeMillis(), 0.0f); - - while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)) - { - accumulator += iterator.next().samples()[2]; - count++; - } - } - - return count + (long)(accumulator * 0); - } - - private long calibrateVector256(short[] samples, short[] residualI, short[] residualQ) - { - long start = System.currentTimeMillis(); - double accumulator = 0.0; - long count = 0; - - while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) - { - SampleInterleavedBufferIteratorVector256Bits iterator = - new SampleInterleavedBufferIteratorVector256Bits(samples, residualI, residualQ, 0.0f, System.currentTimeMillis(), 0.0f); - - while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)) - { - accumulator += iterator.next().samples()[2]; - count++; - } - } - - return count + (long)(accumulator * 0); - } - - private long calibrateVector512(short[] samples, short[] residualI, short[] residualQ) - { - long start = System.currentTimeMillis(); - double accumulator = 0.0; - long count = 0; - - while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) - { - SampleInterleavedBufferIteratorVector512Bits iterator = - new SampleInterleavedBufferIteratorVector512Bits(samples, residualI, residualQ, 0.0f, - System.currentTimeMillis(), 0.0f); - - while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)) - { - accumulator += iterator.next().samples()[2]; - count++; - } - } - - return count + (long)(accumulator * 0); - } -} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/sample/UnpackedSampleConverterCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/sample/UnpackedSampleConverterCalibration.java deleted file mode 100644 index 822d703e..00000000 --- a/src/main/java/io/github/dsheirer/vector/calibrate/sample/UnpackedSampleConverterCalibration.java +++ /dev/null @@ -1,313 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2026 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.vector.calibrate.sample; - -import io.github.dsheirer.buffer.sample.SampleBufferIterator; -import io.github.dsheirer.buffer.sample.SampleBufferIteratorScalar; -import io.github.dsheirer.buffer.sample.SampleBufferIteratorVector128Bits; -import io.github.dsheirer.buffer.sample.SampleBufferIteratorVector256Bits; -import io.github.dsheirer.buffer.sample.SampleBufferIteratorVector512Bits; -import io.github.dsheirer.buffer.sample.SampleBufferIteratorVector64Bits; -import io.github.dsheirer.vector.calibrate.Calibration; -import io.github.dsheirer.vector.calibrate.CalibrationException; -import io.github.dsheirer.vector.calibrate.CalibrationType; -import io.github.dsheirer.vector.calibrate.Implementation; -import jdk.incubator.vector.FloatVector; -import org.apache.commons.math3.stat.descriptive.moment.Mean; - -/** - * Calculates optimal implementation (SCALAR vs VECTOR) for non-interleaved sample native buffers. - */ -public class UnpackedSampleConverterCalibration extends Calibration -{ - private static final int BUFFER_SIZE = 131072; - private static final int ITERATION_DURATION_MS = 1000; - private static final int WARMUP_ITERATIONS = 5; - private static final int TEST_ITERATIONS = 5; - - /** - * Constructs an instance - */ - public UnpackedSampleConverterCalibration() - { - super(CalibrationType.SAMPLE_UNPACKED_ITERATOR); - } - - @Override - public void calibrate() throws CalibrationException - { - short[] samples = getShortSamples(BUFFER_SIZE); - short[] residualI = getShortSamples(SampleBufferIterator.I_OVERLAP); - short[] residualQ = getShortSamples(SampleBufferIterator.Q_OVERLAP); - - mLog.info("UNPACKED SAMPLE CONVERTER - VECTOR SIMD LANES PREFERRED: " + FloatVector.SPECIES_PREFERRED.length()); - - //Warm-Up Phase .... - Mean scalarMean = new Mean(); - - for(int warmup = 0; warmup < WARMUP_ITERATIONS; warmup++) - { - long score = testScalar(samples, residualI, residualQ); - scalarMean.increment(score); - } - - mLog.info("UNPACKED SAMPLE CONVERTER WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); - - switch(FloatVector.SPECIES_PREFERRED.length()) - { - //Deliberate fall-through of each case statement so that we can test the largest - //SIMD lane width supported by hardware down to the smallest SIMD lane width. - case 16: - { - Mean vector512Mean = new Mean(); - for(int warmup = 0; warmup < WARMUP_ITERATIONS; warmup++) - { - long score = testVector512(samples, residualI, residualQ); - vector512Mean.increment(score); - } - - mLog.info("UNPACKED SAMPLE CONVERTER WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); - } - case 8: - { - Mean vector256Mean = new Mean(); - for(int warmup = 0; warmup < WARMUP_ITERATIONS; warmup++) - { - long score = testVector256(samples, residualI, residualQ); - vector256Mean.increment(score); - } - - mLog.info("UNPACKED SAMPLE CONVERTER WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); - } - case 4: - { - Mean vector128Mean = new Mean(); - for(int warmup = 0; warmup < WARMUP_ITERATIONS; warmup++) - { - long score = testVector128(samples, residualI, residualQ); - vector128Mean.increment(score); - } - - mLog.info("UNPACKED SAMPLE CONVERTER WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); - } - case 2: - { - Mean vector64Mean = new Mean(); - for(int warmup = 0; warmup < WARMUP_ITERATIONS; warmup++) - { - long score = testVector64(samples, residualI, residualQ); - vector64Mean.increment(score); - } - - mLog.info("UNPACKED SAMPLE CONVERTER WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); - } - } - - //Test Phase .... - scalarMean.clear(); - - for(int test = 0; test < TEST_ITERATIONS; test++) - { - long score = testScalar(samples, residualI, residualQ); - scalarMean.increment(score); - } - - double bestScore = scalarMean.getResult(); - setImplementation(Implementation.SCALAR); - mLog.info("UNPACKED SAMPLE CONVERTER - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); - - switch(FloatVector.SPECIES_PREFERRED.length()) - { - //Deliberate fall-through of each case statement so that we can test the largest - //SIMD lane width supported by hardware down to the smallest SIMD lane width. - case 16: - { - Mean vector512Mean = new Mean(); - for(int test = 0; test < TEST_ITERATIONS; test++) - { - long score = testVector512(samples, residualI, residualQ); - vector512Mean.increment(score); - } - - mLog.info("UNPACKED SAMPLE CONVERTER - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); - if(vector512Mean.getResult() > bestScore) - { - bestScore = vector512Mean.getResult(); - setImplementation(Implementation.VECTOR_SIMD_512); - } - } - case 8: - { - Mean vector256Mean = new Mean(); - for(int test = 0; test < TEST_ITERATIONS; test++) - { - long score = testVector256(samples, residualI, residualQ); - vector256Mean.increment(score); - } - - mLog.info("UNPACKED SAMPLE CONVERTER - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); - if(vector256Mean.getResult() > bestScore) - { - bestScore = vector256Mean.getResult(); - setImplementation(Implementation.VECTOR_SIMD_256); - } - } - case 4: - { - Mean vector128Mean = new Mean(); - for(int test = 0; test < TEST_ITERATIONS; test++) - { - long score = testVector128(samples, residualI, residualQ); - vector128Mean.increment(score); - } - - mLog.info("UNPACKED SAMPLE CONVERTER - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); - if(vector128Mean.getResult() > bestScore) - { - bestScore = vector128Mean.getResult(); - setImplementation(Implementation.VECTOR_SIMD_128); - } - } - case 2: - { - Mean vector64Mean = new Mean(); - for(int test = 0; test < TEST_ITERATIONS; test++) - { - long score = testVector64(samples, residualI, residualQ); - vector64Mean.increment(score); - } - - mLog.info("UNPACKED SAMPLE CONVERTER - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); - if(vector64Mean.getResult() > bestScore) - { - setImplementation(Implementation.VECTOR_SIMD_64); - } - } - } - - mLog.info("UNPACKED SAMPLE CONVERTER - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation()); - } - - private long testScalar(short[] samples, short[] residualI, short[] residualQ) - { - long start = System.currentTimeMillis(); - double accumulator = 0.0; - long count = 0; - - while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) - { - SampleBufferIteratorScalar iterator = new SampleBufferIteratorScalar(samples, residualI, - residualQ, 0.0f, System.currentTimeMillis(), 0.0f); - - while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)) - { - accumulator += iterator.next().i()[2]; - count++; - } - } - - return count + (long)(accumulator * 0); - } - - private long testVector64(short[] samples, short[] residualI, short[] residualQ) - { - long start = System.currentTimeMillis(); - double accumulator = 0.0; - long count = 0; - - while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) - { - SampleBufferIteratorVector64Bits iterator = new SampleBufferIteratorVector64Bits(samples, residualI, - residualQ, 0.0f, System.currentTimeMillis(), 0.0f); - - while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)) - { - accumulator += iterator.next().i()[2]; - count++; - } - } - - return count + (long)(accumulator * 0); - } - - private long testVector128(short[] samples, short[] residualI, short[] residualQ) - { - long start = System.currentTimeMillis(); - double accumulator = 0.0; - long count = 0; - while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) - { - SampleBufferIteratorVector128Bits iterator = new SampleBufferIteratorVector128Bits(samples, residualI, - residualQ, 0.0f, System.currentTimeMillis(), 0.0f); - - while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)) - { - accumulator += iterator.next().i()[2]; - count++; - } - } - - return count + (long)(accumulator * 0); - } - - private long testVector256(short[] samples, short[] residualI, short[] residualQ) - { - long start = System.currentTimeMillis(); - double accumulator = 0.0; - long count = 0; - - while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) - { - SampleBufferIteratorVector256Bits iterator = - new SampleBufferIteratorVector256Bits(samples, residualI, residualQ, 0.0f, - System.currentTimeMillis(), 0.0f); - - while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)) - { - accumulator += iterator.next().i()[2]; - count++; - } - } - - return count + (long)(accumulator * 0); - } - - private long testVector512(short[] samples, short[] residualI, short[] residualQ) - { - long start = System.currentTimeMillis(); - double accumulator = 0.0; - long count = 0; - - while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) - { - SampleBufferIteratorVector512Bits iterator = - new SampleBufferIteratorVector512Bits(samples, residualI, residualQ, 0.0f, - System.currentTimeMillis(), 0.0f); - - while(iterator.hasNext() && ((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)) - { - accumulator += iterator.next().i()[2]; - count++; - } - } - - return count + (long)(accumulator * 0); - } -}