summaryrefslogtreecommitdiff
path: root/Radio/HW/AirSpyHF
diff options
context:
space:
mode:
Diffstat (limited to 'Radio/HW/AirSpyHF')
-rw-r--r--Radio/HW/AirSpyHF/src/airspyhf.c1910
-rw-r--r--Radio/HW/AirSpyHF/src/airspyhf.h171
-rw-r--r--Radio/HW/AirSpyHF/src/airspyhf_commands.h63
-rw-r--r--Radio/HW/AirSpyHF/src/iqbalancer.c564
-rw-r--r--Radio/HW/AirSpyHF/src/iqbalancer.h68
5 files changed, 2776 insertions, 0 deletions
diff --git a/Radio/HW/AirSpyHF/src/airspyhf.c b/Radio/HW/AirSpyHF/src/airspyhf.c
new file mode 100644
index 0000000..efacd03
--- /dev/null
+++ b/Radio/HW/AirSpyHF/src/airspyhf.c
@@ -0,0 +1,1910 @@
+/*
+Copyright (c) 2013-2023, Youssef Touil <youssef@airspy.com>
+Copyright (c) 2013-2017, Ian Gilmour <ian@sdrsharp.com>
+Copyright (c) 2013-2017, Benjamin Vernoux <bvernoux@airspy.com>
+Copyright (c) 2013, Michael Ossmann <mike@ossmann.com>
+Copyright (c) 2012, Jared Boone <jared@sharebrained.com>
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+ Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ Neither the name of Airspy HF+ nor the names of its contributors may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifdef _WIN32
+#define HAVE_STRUCT_TIMESPEC
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "libusb.h"
+#include <pthread.h>
+#include <math.h>
+
+#include "iqbalancer.h"
+#include "airspyhf.h"
+#include "airspyhf_commands.h"
+
+#ifndef bool
+typedef int bool;
+#define true 1
+#define false 0
+#endif
+
+#ifndef M_PI
+#define M_PI (3.14159265359)
+#endif
+
+#define MAX(a,b) ((a) > (b) ? a : b)
+#define MIN(a,b) ((a) < (b) ? a : b)
+
+#define SAMPLES_TO_TRANSFER (1024 * 2)
+#define SERIAL_NUMBER_UNUSED (0)
+#define FILE_DESCRIPTOR_UNUSED (-1)
+#define RAW_BUFFER_COUNT (8)
+#define AIRSPYHF_SERIAL_SIZE (28)
+
+#define MAX_SAMPLERATE_INDEX (100)
+#define DEFAULT_SAMPLERATE (768000)
+
+#define DEFAULT_ATT_STEP_COUNT (9)
+#define DEFAULT_ATT_STEP_INCREMENT (6.0f)
+
+#define CALIBRATION_MAGIC (0xA5CA71B0)
+
+#define DEFAULT_IF_SHIFT (5000)
+#define MIN_ZERO_IF_LO (180)
+#define MIN_LOW_IF_LO (84)
+
+#define STR_PREFIX_SERIAL_AIRSPYHF_SIZE (12)
+static const char str_prefix_serial_airspyhf[STR_PREFIX_SERIAL_AIRSPYHF_SIZE] =
+{ 'A', 'I', 'R', 'S', 'P', 'Y', 'H', 'F', ' ', 'S', 'N', ':' };
+
+#define INITIAL_PHASE (0.00006f)
+#define INITIAL_AMPLITUDE (-0.0045f)
+
+#define LIBUSB_CTRL_TIMEOUT_MS (500)
+
+#define IQ_BALANCER_EVAL_SKIP (RAW_BUFFER_COUNT)
+
+#pragma pack(push,1)
+
+typedef struct {
+ int16_t im;
+ int16_t re;
+} airspyhf_complex_int16_t;
+
+#pragma pack(pop)
+
+typedef struct airspyhf_device
+{
+ libusb_context* usb_context;
+ libusb_device_handle* usb_device;
+ struct libusb_transfer** transfers;
+ airspyhf_sample_block_cb_fn callback;
+ pthread_t transfer_thread;
+ pthread_t consumer_thread;
+ bool transfer_thread_running;
+ bool consumer_thread_running;
+ pthread_cond_t consumer_cv;
+ pthread_mutex_t consumer_mp;
+ uint32_t supported_samplerate_count;
+ uint32_t *supported_samplerates;
+ uint8_t *samplerate_architectures;
+ uint32_t supported_att_step_count;
+ float *supported_att_steps;
+ volatile uint32_t current_samplerate;
+ volatile double freq_hz;
+ volatile uint32_t freq_khz;
+ volatile double freq_delta_hz;
+ volatile double freq_shift;
+ volatile int32_t calibration_ppb;
+ volatile int32_t calibration_vctcxo;
+ volatile uint32_t frontend_options;
+ volatile float optimal_point;
+ uint8_t enable_dsp;
+ uint8_t is_low_if;
+ float filter_gain;
+ airspyhf_complex_float_t vec;
+ struct iq_balancer_t *iq_balancer;
+ volatile int32_t iq_balancer_eval_skip;
+ uint32_t transfer_count;
+ int32_t transfer_live;
+ uint32_t buffer_size;
+ uint32_t dropped_buffers;
+ uint32_t dropped_buffers_queue[RAW_BUFFER_COUNT];
+ airspyhf_complex_int16_t *received_samples_queue[RAW_BUFFER_COUNT];
+ volatile bool streaming;
+ volatile bool stop_requested;
+ volatile int received_samples_queue_head;
+ volatile int received_samples_queue_tail;
+ volatile int received_buffer_count;
+ airspyhf_complex_float_t *output_buffer;
+ void* ctx;
+} airspyhf_device_t;
+
+typedef struct flash_config
+{
+ uint32_t magic_number;
+ int32_t calibration_ppb;
+ int32_t calibration_vctcxo;
+ uint32_t frontend_options;
+} flash_config_t;
+
+static const uint16_t airspyhf_usb_vid = 0x03EB;
+static const uint16_t airspyhf_usb_pid = 0x800C;
+
+static int airspyhf_config_read(airspyhf_device_t* device, uint8_t *buffer, uint16_t length);
+
+static int cancel_transfers(airspyhf_device_t* device)
+{
+ uint32_t transfer_index;
+ struct timeval canceltv = { 0, 50 };
+
+ if (device->transfers != NULL)
+ {
+ for (transfer_index = 0; transfer_index<device->transfer_count; transfer_index++)
+ {
+ if (device->transfers[transfer_index] != NULL)
+ {
+ libusb_cancel_transfer(device->transfers[transfer_index]);
+ }
+ }
+
+ for (transfer_index = 0; transfer_index < device->transfer_count && device->transfer_live > 0; transfer_index++)
+ libusb_handle_events_timeout_completed(device->usb_context, &canceltv, NULL);
+
+ return AIRSPYHF_SUCCESS;
+ }
+ else
+ {
+ return AIRSPYHF_ERROR;
+ }
+}
+
+static int free_transfers(airspyhf_device_t* device)
+{
+ int i;
+ uint32_t transfer_index;
+
+ if (device->transfers != NULL)
+ {
+ free(device->output_buffer);
+ device->output_buffer = NULL;
+
+ for (transfer_index = 0; transfer_index < device->transfer_count; transfer_index++)
+ {
+ if (device->transfers[transfer_index] != NULL)
+ {
+ libusb_free_transfer(device->transfers[transfer_index]);
+ free(device->transfers[transfer_index]->buffer);
+ device->transfers[transfer_index] = NULL;
+ }
+ }
+ free(device->transfers);
+ device->transfers = NULL;
+
+ for (i = 0; i < RAW_BUFFER_COUNT; i++)
+ {
+ if (device->received_samples_queue[i] != NULL)
+ {
+ free(device->received_samples_queue[i]);
+ device->received_samples_queue[i] = NULL;
+ }
+ }
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+static int allocate_transfers(airspyhf_device_t* const device)
+{
+ int i;
+ uint32_t transfer_index;
+
+ if (device->transfers == NULL)
+ {
+ device->output_buffer = (airspyhf_complex_float_t *) malloc((device->buffer_size / sizeof(airspyhf_complex_int16_t)) * sizeof(airspyhf_complex_float_t));
+
+ for (i = 0; i < RAW_BUFFER_COUNT; i++)
+ {
+ device->received_samples_queue[i] = (airspyhf_complex_int16_t *) malloc(device->buffer_size);
+ if (device->received_samples_queue[i] == NULL)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ memset(device->received_samples_queue[i], 0, device->buffer_size);
+ }
+
+ device->transfers = (struct libusb_transfer**) calloc(device->transfer_count, sizeof(struct libusb_transfer));
+ if (device->transfers == NULL)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ for (transfer_index = 0; transfer_index<device->transfer_count; transfer_index++)
+ {
+ device->transfers[transfer_index] = libusb_alloc_transfer(0);
+ if (device->transfers[transfer_index] == NULL)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ libusb_fill_bulk_transfer(
+ device->transfers[transfer_index],
+ device->usb_device,
+ 0,
+ (unsigned char*) malloc(device->buffer_size),
+ device->buffer_size,
+ NULL,
+ device,
+ 0);
+
+ if (device->transfers[transfer_index]->buffer == NULL)
+ {
+ return AIRSPYHF_ERROR;
+ }
+ }
+ return AIRSPYHF_SUCCESS;
+ }
+ else
+ {
+ return AIRSPYHF_ERROR;
+ }
+}
+
+static int prepare_transfers(airspyhf_device_t* device, const uint_fast8_t endpoint_address, libusb_transfer_cb_fn callback)
+{
+ int error;
+ uint32_t transfer_index;
+
+ if (device->transfers != NULL)
+ {
+ for (transfer_index = 0; transfer_index<device->transfer_count; transfer_index++)
+ {
+ device->transfers[transfer_index]->endpoint = endpoint_address;
+ device->transfers[transfer_index]->callback = callback;
+
+ error = libusb_submit_transfer(device->transfers[transfer_index]);
+ if (error != 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+ else
+ device->transfer_live++;
+ }
+ return AIRSPYHF_SUCCESS;
+ }
+
+ return AIRSPYHF_ERROR;
+}
+
+static void multiply_complex_complex(airspyhf_complex_float_t *a, const airspyhf_complex_float_t *b)
+{
+ float re = a->re * b->re - a->im * b->im;
+ a->im = a->im * b->re + a->re * b->im;
+ a->re = re;
+}
+
+static void multiply_complex_real(airspyhf_complex_float_t *a, const float b)
+{
+ a->re *= b;
+ a->im *= b;
+}
+
+static inline void rotate_complex(airspyhf_complex_float_t *vec, const airspyhf_complex_float_t *rot)
+{
+ multiply_complex_complex(vec, rot);
+
+ float norm = 1.99f - (vec->re * vec->re + vec->im * vec->im);
+
+ multiply_complex_real(vec, norm);
+}
+
+static void convert_samples(airspyhf_device_t* device, airspyhf_complex_int16_t *src, airspyhf_complex_float_t *dest, int count)
+{
+ const float scale = 1.0f / 32768;
+
+ int i;
+ airspyhf_complex_float_t vec;
+ airspyhf_complex_float_t rot;
+ double angle;
+ float conversion_gain;
+ uint8_t iqb_eval_skip = 0;
+
+ conversion_gain = scale * device->filter_gain;
+
+ for (i = 0; i < count; i++)
+ {
+ dest[i].re = src[i].re * conversion_gain;
+ dest[i].im = src[i].im * conversion_gain;
+ }
+
+ if (device->iq_balancer_eval_skip > 0)
+ {
+ device->iq_balancer_eval_skip--;
+ iqb_eval_skip = 1;
+ }
+
+ if (device->enable_dsp)
+ {
+ if (!device->is_low_if)
+ {
+ // Zero IF requires external IQ correction
+ iq_balancer_process(device->iq_balancer, dest, count, iqb_eval_skip);
+ }
+
+ // Fine tuning
+ if (device->freq_shift != 0)
+ {
+ angle = 2.0 * M_PI * device->freq_shift / (double) device->current_samplerate;
+
+ vec = device->vec;
+
+ rot.re = (float) cos(angle);
+ rot.im = (float) -sin(angle);
+
+ for (i = 0; i < count; i++)
+ {
+ rotate_complex(&vec, &rot);
+ multiply_complex_complex(&dest[i], &vec);
+ }
+
+ device->vec = vec;
+ }
+ }
+}
+
+static void* consumer_threadproc(void *arg)
+{
+ int sample_count;
+ airspyhf_complex_int16_t *input_samples;
+ uint32_t dropped_buffers;
+ airspyhf_device_t* device = (airspyhf_device_t*) arg;
+ airspyhf_transfer_t transfer;
+
+#ifdef _WIN32
+
+ SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
+
+#endif
+
+ pthread_mutex_lock(&device->consumer_mp);
+
+ while (device->streaming && !device->stop_requested)
+ {
+ while (device->received_buffer_count == 0 && device->streaming && !device->stop_requested)
+ {
+ pthread_cond_wait(&device->consumer_cv, &device->consumer_mp);
+ }
+ if (!device->streaming || device->stop_requested)
+ {
+ break;
+ }
+
+ input_samples = (airspyhf_complex_int16_t *) device->received_samples_queue[device->received_samples_queue_tail];
+ dropped_buffers = device->dropped_buffers_queue[device->received_samples_queue_tail];
+ device->received_samples_queue_tail = (device->received_samples_queue_tail + 1) & (RAW_BUFFER_COUNT - 1);
+
+ pthread_mutex_unlock(&device->consumer_mp);
+
+ sample_count = device->buffer_size / sizeof(airspyhf_complex_int16_t);
+
+ convert_samples(device, input_samples, device->output_buffer, sample_count);
+
+ transfer.device = device;
+ transfer.ctx = device->ctx;
+ transfer.samples = device->output_buffer;
+ transfer.sample_count = sample_count;
+ transfer.dropped_samples = (uint64_t) dropped_buffers * (uint64_t) sample_count;
+
+ if (device->callback(&transfer) != 0)
+ {
+ device->streaming = false;
+ }
+
+ pthread_mutex_lock(&device->consumer_mp);
+ device->received_buffer_count--;
+ }
+
+ device->streaming = false;
+
+ pthread_mutex_unlock(&device->consumer_mp);
+
+ return NULL;
+}
+
+static void LIBUSB_CALL airspyhf_libusb_transfer_callback(struct libusb_transfer* usb_transfer)
+{
+ airspyhf_complex_int16_t *temp;
+ airspyhf_device_t* device = (airspyhf_device_t*) usb_transfer->user_data;
+
+ device->transfer_live--;
+ if (!device->streaming || device->stop_requested)
+ {
+ return;
+ }
+
+ if (usb_transfer->status == LIBUSB_TRANSFER_COMPLETED && usb_transfer->actual_length == usb_transfer->length)
+ {
+ pthread_mutex_lock(&device->consumer_mp);
+
+ if (device->received_buffer_count < RAW_BUFFER_COUNT)
+ {
+ temp = device->received_samples_queue[device->received_samples_queue_head];
+ device->received_samples_queue[device->received_samples_queue_head] = (airspyhf_complex_int16_t *) usb_transfer->buffer;
+ usb_transfer->buffer = (uint8_t *) temp;
+
+ device->dropped_buffers_queue[device->received_samples_queue_head] = device->dropped_buffers;
+ device->dropped_buffers = 0;
+
+ device->received_samples_queue_head = (device->received_samples_queue_head + 1) & (RAW_BUFFER_COUNT - 1);
+ device->received_buffer_count++;
+
+ pthread_cond_signal(&device->consumer_cv);
+ }
+ else
+ {
+ device->dropped_buffers++;
+ }
+
+ pthread_mutex_unlock(&device->consumer_mp);
+
+ if (libusb_submit_transfer(usb_transfer) != 0)
+ {
+ device->streaming = false;
+ }
+ else
+ device->transfer_live ++;
+ }
+ else
+ {
+ device->streaming = false;
+ }
+}
+
+static void* transfer_threadproc(void* arg)
+{
+ airspyhf_device_t* device = (struct airspyhf_device*) arg;
+ int error;
+ struct timeval timeout = { 0, 500000 };
+
+#ifdef _WIN32
+
+ SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
+
+#endif
+
+ while (device->streaming && !device->stop_requested)
+ {
+ error = libusb_handle_events_timeout_completed(device->usb_context, &timeout, NULL);
+ if (error < 0)
+ {
+ if (error != LIBUSB_ERROR_INTERRUPTED)
+ device->streaming = false;
+ }
+ }
+
+ device->streaming = false;
+
+ return NULL;
+}
+
+static int kill_io_threads(airspyhf_device_t* device)
+{
+ struct timeval timeout = { 0, 0 };
+
+ if (device->stop_requested)
+ {
+ device->stop_requested = false;
+ device->streaming = false;
+ cancel_transfers(device);
+
+ pthread_mutex_lock(&device->consumer_mp);
+ pthread_cond_signal(&device->consumer_cv);
+ pthread_mutex_unlock(&device->consumer_mp);
+
+ if (device->transfer_thread_running) {
+ pthread_join(device->transfer_thread, NULL);
+ device->transfer_thread_running = false;
+ }
+ if (device->consumer_thread_running) {
+ pthread_join(device->consumer_thread, NULL);
+ device->consumer_thread_running = false;
+ }
+
+ libusb_handle_events_timeout_completed(device->usb_context, &timeout, NULL);
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+static int create_io_threads(airspyhf_device_t* device, airspyhf_sample_block_cb_fn callback)
+{
+ int result;
+ pthread_attr_t attr;
+
+ if (!device->streaming && !device->stop_requested)
+ {
+ device->callback = callback;
+ device->streaming = true;
+
+ result = prepare_transfers(device, LIBUSB_ENDPOINT_IN | AIRSPYHF_ENDPOINT_IN, (libusb_transfer_cb_fn)airspyhf_libusb_transfer_callback);
+ if (result != AIRSPYHF_SUCCESS)
+ {
+ return result;
+ }
+
+ device->received_samples_queue_head = 0;
+ device->received_samples_queue_tail = 0;
+ device->received_buffer_count = 0;
+
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+
+ result = pthread_create(&device->consumer_thread, &attr, consumer_threadproc, device);
+ if (result != 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+ device->consumer_thread_running = true;
+
+ result = pthread_create(&device->transfer_thread, &attr, transfer_threadproc, device);
+ if (result != 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+ device->transfer_thread_running = true;
+
+ pthread_attr_destroy(&attr);
+ }
+ else {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+static void airspyhf_open_exit(airspyhf_device_t* device)
+{
+ if (device->usb_device != NULL)
+ {
+ libusb_release_interface(device->usb_device, 0);
+ libusb_close(device->usb_device);
+ device->usb_device = NULL;
+ }
+ libusb_exit(device->usb_context);
+ device->usb_context = NULL;
+}
+
+static int airspyhf_read_samplerates_from_fw(airspyhf_device_t* device, uint32_t* buffer, const uint32_t len)
+{
+ int result;
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_GET_SAMPLERATES,
+ 0,
+ len,
+ (unsigned char*) buffer,
+ (len > 0 ? len : 1) * (int16_t) sizeof(uint32_t),
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 1)
+ {
+ return AIRSPYHF_ERROR;
+ }
+ return AIRSPYHF_SUCCESS;
+}
+
+static int airspyhf_read_att_steps_from_fw(airspyhf_device_t* device, void* buffer, const uint32_t count)
+{
+ int result;
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_GET_ATT_STEPS,
+ 0,
+ count,
+ (unsigned char*) buffer,
+ (int16_t)(count > 0 ? (count * sizeof(float)) : sizeof(int32_t)),
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 1)
+ {
+ return AIRSPYHF_ERROR;
+ }
+ return AIRSPYHF_SUCCESS;
+}
+
+static int airspyhf_read_samplerate_architectures_from_fw(airspyhf_device_t* device, uint8_t* buffer, const uint32_t len)
+{
+ int result;
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_GET_SAMPLERATE_ARCHITECTURES,
+ 0,
+ len,
+ (unsigned char*) buffer,
+ (len > 0 ? len : 1) * (int16_t) sizeof(uint32_t),
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 1)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+
+static void airspyhf_open_device(airspyhf_device_t* device,
+ int* ret,
+ uint16_t vid,
+ uint16_t pid,
+ uint64_t serial_number_val)
+{
+ int i;
+ int result;
+ libusb_device_handle** libusb_dev_handle;
+ int serial_number_len;
+ libusb_device_handle* dev_handle;
+ libusb_device *dev;
+ libusb_device** devices = NULL;
+
+ ssize_t cnt;
+ int serial_descriptor_index;
+ struct libusb_device_descriptor device_descriptor;
+ unsigned char serial_number[AIRSPYHF_SERIAL_SIZE + 1];
+
+ libusb_dev_handle = &device->usb_device;
+ *libusb_dev_handle = NULL;
+
+ cnt = libusb_get_device_list(device->usb_context, &devices);
+ if (cnt < 0)
+ {
+ *ret = AIRSPYHF_ERROR;
+ return;
+ }
+
+ i = 0;
+ while ((dev = devices[i++]) != NULL)
+ {
+ libusb_get_device_descriptor(dev, &device_descriptor);
+
+ if ((device_descriptor.idVendor == vid) &&
+ (device_descriptor.idProduct == pid))
+ {
+ if (serial_number_val != SERIAL_NUMBER_UNUSED)
+ {
+ serial_descriptor_index = device_descriptor.iSerialNumber;
+ if (serial_descriptor_index > 0)
+ {
+ if (libusb_open(dev, libusb_dev_handle) != 0)
+ {
+ *libusb_dev_handle = NULL;
+ continue;
+ }
+ dev_handle = *libusb_dev_handle;
+ serial_number_len = libusb_get_string_descriptor_ascii(dev_handle,
+ serial_descriptor_index,
+ serial_number,
+ sizeof(serial_number));
+
+ if (serial_number_len == AIRSPYHF_SERIAL_SIZE && !memcmp(str_prefix_serial_airspyhf, serial_number, STR_PREFIX_SERIAL_AIRSPYHF_SIZE))
+ {
+ uint64_t serial = SERIAL_NUMBER_UNUSED;
+ // use same code to determine device's serial number as in airspyhf_list_devices()
+ {
+ char *start, *end;
+ serial_number[AIRSPYHF_SERIAL_SIZE] = 0;
+ start = (char*)(serial_number + STR_PREFIX_SERIAL_AIRSPYHF_SIZE);
+ end = NULL;
+ serial = strtoull(start, &end, 16);
+ }
+ if (serial == serial_number_val)
+ {
+ result = libusb_set_configuration(dev_handle, 1);
+ if (result != 0)
+ {
+ libusb_close(dev_handle);
+ *libusb_dev_handle = NULL;
+ continue;
+ }
+ result = libusb_claim_interface(dev_handle, 0);
+ if (result != 0)
+ {
+ libusb_close(dev_handle);
+ *libusb_dev_handle = NULL;
+ continue;
+ }
+
+ result = libusb_set_interface_alt_setting(dev_handle, 0, 1);
+ if (result != 0)
+ {
+ libusb_close(dev_handle);
+ *libusb_dev_handle = NULL;
+ continue;
+ }
+
+ break;
+ }
+ else
+ {
+ libusb_close(dev_handle);
+ *libusb_dev_handle = NULL;
+ continue;
+ }
+ }
+ else
+ {
+ libusb_close(dev_handle);
+ *libusb_dev_handle = NULL;
+ continue;
+ }
+ }
+ }
+ else
+ {
+ if (libusb_open(dev, libusb_dev_handle) == 0)
+ {
+ dev_handle = *libusb_dev_handle;
+ result = libusb_set_configuration(dev_handle, 1);
+ if (result != 0)
+ {
+ libusb_close(dev_handle);
+ *libusb_dev_handle = NULL;
+ continue;
+ }
+ result = libusb_claim_interface(dev_handle, 0);
+ if (result != 0)
+ {
+ libusb_close(dev_handle);
+ *libusb_dev_handle = NULL;
+ continue;
+ }
+
+ result = libusb_set_interface_alt_setting(dev_handle, 0, 1);
+ if (result != 0)
+ {
+ libusb_close(dev_handle);
+ *libusb_dev_handle = NULL;
+ continue;
+ }
+ break;
+ }
+ }
+ }
+ }
+ libusb_free_device_list(devices, 1);
+
+ dev_handle = device->usb_device;
+ if (dev_handle == NULL)
+ {
+ *ret = AIRSPYHF_ERROR;
+ return;
+ }
+
+ *ret = AIRSPYHF_SUCCESS;
+ return;
+}
+
+static void airspyhf_open_device_fd(airspyhf_device_t* device,
+ int* ret,
+ int fd) {
+ int result = -1;
+
+#ifdef __ANDROID__
+ result = libusb_wrap_sys_device(device->usb_context, (intptr_t)fd, &device->usb_device);
+#else
+ device->usb_device = NULL;
+ *ret = AIRSPYHF_UNSUPPORTED;
+ return;
+#endif
+
+ if (result != 0 || device->usb_device == NULL)
+ {
+ *ret = AIRSPYHF_ERROR;
+ return;
+ }
+
+ result = libusb_set_configuration(device->usb_device, 1);
+ if (result != 0)
+ {
+ libusb_close(device->usb_device);
+ device->usb_device = NULL;
+ *ret = AIRSPYHF_ERROR;
+ return;
+ }
+
+ result = libusb_claim_interface(device->usb_device, 0);
+ if (result != 0)
+ {
+ libusb_close(device->usb_device);
+ device->usb_device = NULL;
+ *ret = AIRSPYHF_ERROR;
+ return;
+ }
+
+ result = libusb_set_interface_alt_setting(device->usb_device, 0, 1);
+ if (result != 0)
+ {
+ libusb_close(device->usb_device);
+ device->usb_device = NULL;
+ *ret = AIRSPYHF_ERROR;
+ return;
+ }
+
+ *ret = AIRSPYHF_SUCCESS;
+ return;
+}
+
+int airspyhf_list_devices(uint64_t *serials, int count)
+{
+ libusb_device_handle* libusb_dev_handle;
+ struct libusb_context *context;
+ libusb_device** devices = NULL;
+ libusb_device *dev;
+ struct libusb_device_descriptor device_descriptor;
+
+ int serial_descriptor_index;
+ int serial_number_len;
+ int output_count;
+ int i;
+ unsigned char serial_number[AIRSPYHF_SERIAL_SIZE + 1];
+
+ if (serials)
+ {
+ memset(serials, 0, sizeof(uint64_t) * count);
+ }
+
+#ifdef __ANDROID__
+ // LibUSB does not support device discovery on android
+ libusb_set_option(NULL, LIBUSB_OPTION_NO_DEVICE_DISCOVERY, NULL);
+#endif
+
+ if (libusb_init(&context) != 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ if (libusb_get_device_list(context, &devices) < 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ i = 0;
+ output_count = 0;
+ while ((dev = devices[i++]) != NULL && (!serials || output_count < count))
+ {
+ libusb_get_device_descriptor(dev, &device_descriptor);
+
+ if ((device_descriptor.idVendor == airspyhf_usb_vid) &&
+ (device_descriptor.idProduct == airspyhf_usb_pid))
+ {
+ serial_descriptor_index = device_descriptor.iSerialNumber;
+ if (serial_descriptor_index > 0)
+ {
+ if (libusb_open(dev, &libusb_dev_handle) != 0)
+ {
+ continue;
+ }
+
+ serial_number_len = libusb_get_string_descriptor_ascii(libusb_dev_handle,
+ serial_descriptor_index,
+ serial_number,
+ sizeof(serial_number));
+
+ if (serial_number_len == AIRSPYHF_SERIAL_SIZE && !memcmp(str_prefix_serial_airspyhf, serial_number, STR_PREFIX_SERIAL_AIRSPYHF_SIZE))
+ {
+ char *start, *end;
+ uint64_t serial;
+
+ serial_number[AIRSPYHF_SERIAL_SIZE] = 0;
+ start = (char*)(serial_number + STR_PREFIX_SERIAL_AIRSPYHF_SIZE);
+ end = NULL;
+ serial = strtoull(start, &end, 16);
+ if (serial == 0 && start == end)
+ {
+ libusb_close(libusb_dev_handle);
+ continue;
+ }
+
+ if (serials)
+ {
+ serials[output_count] = serial;
+ }
+ output_count++;
+ }
+
+ libusb_close(libusb_dev_handle);
+ }
+ }
+ }
+
+ libusb_free_device_list(devices, 1);
+ libusb_exit(context);
+ return output_count;
+}
+
+static int airspyhf_open_init(airspyhf_device_t** device, uint64_t serial_number, int fd)
+{
+ airspyhf_device_t* lib_device;
+ flash_config_t config;
+ int libusb_error;
+ int result;
+
+ *device = NULL;
+
+ lib_device = (airspyhf_device_t*) calloc(1, sizeof(airspyhf_device_t));
+ if (lib_device == NULL)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+#ifdef __ANDROID__
+ // LibUSB does not support device discovery on android
+ libusb_set_option(NULL, LIBUSB_OPTION_NO_DEVICE_DISCOVERY, NULL);
+#endif
+
+ libusb_error = libusb_init(&lib_device->usb_context);
+ if (libusb_error != 0)
+ {
+ free(lib_device);
+ return AIRSPYHF_ERROR;
+ }
+
+ if (fd == FILE_DESCRIPTOR_UNUSED) {
+ airspyhf_open_device(lib_device,
+ &result,
+ airspyhf_usb_vid,
+ airspyhf_usb_pid,
+ serial_number);
+ }
+ else {
+ airspyhf_open_device_fd(lib_device,
+ &result,
+ fd);
+ }
+
+ if (lib_device->usb_device == NULL)
+ {
+ libusb_exit(lib_device->usb_context);
+ free(lib_device);
+ return result;
+ }
+
+ lib_device->transfers = NULL;
+ lib_device->callback = NULL;
+ lib_device->transfer_count = 16;
+ lib_device->buffer_size = SAMPLES_TO_TRANSFER * sizeof(airspyhf_complex_int16_t);
+ lib_device->streaming = false;
+ lib_device->stop_requested = false;
+
+ lib_device->supported_samplerates = NULL;
+ lib_device->samplerate_architectures = NULL;
+
+ result = airspyhf_read_samplerates_from_fw(lib_device, &lib_device->supported_samplerate_count, 0);
+ if (result == AIRSPYHF_SUCCESS)
+ {
+ lib_device->supported_samplerates = (uint32_t *)malloc(lib_device->supported_samplerate_count * sizeof(uint32_t));
+ result = airspyhf_read_samplerates_from_fw(lib_device, lib_device->supported_samplerates, lib_device->supported_samplerate_count);
+ if (result == AIRSPYHF_SUCCESS)
+ {
+ lib_device->samplerate_architectures = (uint8_t *)malloc(lib_device->supported_samplerate_count * sizeof(uint8_t));
+ result = airspyhf_read_samplerate_architectures_from_fw(lib_device, lib_device->samplerate_architectures, lib_device->supported_samplerate_count);
+ if (result != AIRSPYHF_SUCCESS)
+ {
+ memset(lib_device->samplerate_architectures, 0, lib_device->supported_samplerate_count * sizeof(uint8_t)); // Assume Zero IF for all
+ result = AIRSPYHF_SUCCESS; // Clear this error for backward compatibility.
+ }
+ }
+ else
+ {
+ free(lib_device->supported_samplerates);
+ lib_device->supported_samplerates = NULL;
+ }
+ }
+
+ if (result != AIRSPYHF_SUCCESS)
+ {
+ // Assume one default sample rate with Zero IF
+
+ lib_device->supported_samplerate_count = 1;
+ lib_device->supported_samplerates = (uint32_t *) malloc(lib_device->supported_samplerate_count * sizeof(uint32_t));
+ lib_device->supported_samplerates[0] = DEFAULT_SAMPLERATE;
+
+ lib_device->samplerate_architectures = (uint8_t *) malloc(lib_device->supported_samplerate_count * sizeof(uint8_t));
+ lib_device->samplerate_architectures[0] = 0;
+ }
+
+ lib_device->current_samplerate = lib_device->supported_samplerates[0];
+ lib_device->is_low_if = lib_device->samplerate_architectures[0];
+
+ result = airspyhf_read_att_steps_from_fw(lib_device, &lib_device->supported_att_step_count, 0);
+ if (result == AIRSPYHF_SUCCESS)
+ {
+ lib_device->supported_att_steps = (float*)malloc(lib_device->supported_att_step_count * sizeof(float));
+ result = airspyhf_read_att_steps_from_fw(lib_device, lib_device->supported_att_steps, lib_device->supported_att_step_count);
+ if (result != AIRSPYHF_SUCCESS)
+ {
+ free(lib_device->supported_att_steps);
+ lib_device->supported_att_steps = NULL;
+ lib_device->supported_att_step_count = 0;
+ }
+ }
+
+ if (result != AIRSPYHF_SUCCESS)
+ {
+ // Assume ATT steps of the Airspy HF+ Discovery
+
+ lib_device->supported_att_step_count = DEFAULT_ATT_STEP_COUNT;
+ lib_device->supported_att_steps = (float*)malloc(lib_device->supported_att_step_count * sizeof(float));
+ for (uint32_t i = 0; i < lib_device->supported_att_step_count; i++)
+ {
+ lib_device->supported_att_steps[i] = i * DEFAULT_ATT_STEP_INCREMENT;
+ }
+ }
+
+ result = allocate_transfers(lib_device);
+ if (result != 0)
+ {
+ airspyhf_open_exit(lib_device);
+ free(lib_device);
+ return AIRSPYHF_ERROR;
+ }
+
+ pthread_cond_init(&lib_device->consumer_cv, NULL);
+ pthread_mutex_init(&lib_device->consumer_mp, NULL);
+
+ lib_device->freq_hz = 0;
+ lib_device->freq_khz = 0;
+ lib_device->freq_delta_hz = 0;
+ lib_device->freq_shift = 0;
+ lib_device->vec.re = 1.0f;
+ lib_device->vec.im = 0.0f;
+ lib_device->optimal_point = 0.0f;
+ lib_device->filter_gain = 1.0f;
+ lib_device->enable_dsp = 1;
+
+ if (airspyhf_config_read(lib_device, (uint8_t *) &config, sizeof(config)) == AIRSPYHF_SUCCESS)
+ {
+ if (config.magic_number == CALIBRATION_MAGIC)
+ {
+ lib_device->calibration_ppb = config.calibration_ppb;
+ airspyhf_set_vctcxo_calibration(lib_device, config.calibration_vctcxo);
+ airspyhf_set_frontend_options(lib_device, config.frontend_options);
+ }
+ else
+ {
+ lib_device->calibration_ppb = 0;
+ lib_device->calibration_vctcxo = 0;
+ lib_device->frontend_options = 0;
+ }
+ }
+ else
+ {
+ lib_device->calibration_ppb = 0;
+ lib_device->calibration_vctcxo = 0;
+ lib_device->frontend_options = 0;
+ }
+
+ lib_device->iq_balancer = iq_balancer_create(INITIAL_PHASE, INITIAL_AMPLITUDE);
+ lib_device->iq_balancer_eval_skip = 0;
+
+ *device = lib_device;
+
+ return AIRSPYHF_SUCCESS;
+}
+
+void ADDCALL airspyhf_lib_version(airspyhf_lib_version_t* lib_version)
+{
+ lib_version->major_version = AIRSPYHF_VER_MAJOR;
+ lib_version->minor_version = AIRSPYHF_VER_MINOR;
+ lib_version->revision = AIRSPYHF_VER_REVISION;
+}
+
+int ADDCALL airspyhf_open_sn(airspyhf_device_t** device, uint64_t serial_number)
+{
+ int result;
+
+ result = airspyhf_open_init(device, serial_number, FILE_DESCRIPTOR_UNUSED);
+ return result;
+}
+
+int ADDCALL airspyhf_open_fd(airspyhf_device_t** device, int fd)
+{
+ int result;
+
+ result = airspyhf_open_init(device, SERIAL_NUMBER_UNUSED, fd);
+ return result;
+}
+
+int ADDCALL airspyhf_open(airspyhf_device_t** device)
+{
+ int result;
+
+ result = airspyhf_open_init(device, SERIAL_NUMBER_UNUSED, FILE_DESCRIPTOR_UNUSED);
+ return result;
+}
+
+int ADDCALL airspyhf_close(airspyhf_device_t* device)
+{
+ int result;
+
+ result = AIRSPYHF_SUCCESS;
+
+ if (device != NULL)
+ {
+ result = airspyhf_stop(device);
+ free_transfers(device);
+ airspyhf_open_exit(device);
+
+ free(device->supported_samplerates);
+ free(device->samplerate_architectures);
+ free(device->supported_att_steps);
+ iq_balancer_destroy(device->iq_balancer);
+
+ pthread_cond_destroy(&device->consumer_cv);
+ pthread_mutex_destroy(&device->consumer_mp);
+
+ free(device);
+ }
+
+ return result;
+}
+
+int ADDCALL airspyhf_get_output_size(airspyhf_device_t * device)
+{
+ // Todo: Make this configurable
+ return SAMPLES_TO_TRANSFER;
+}
+
+int ADDCALL airspyhf_is_low_if(airspyhf_device_t* device)
+{
+ return device->is_low_if;
+}
+
+int ADDCALL airspyhf_get_samplerates(airspyhf_device_t* device, uint32_t* buffer, const uint32_t len)
+{
+ if (len == 0)
+ {
+ *buffer = device->supported_samplerate_count;
+ }
+ else if (len <= device->supported_samplerate_count)
+ {
+ memcpy(buffer, device->supported_samplerates, len * sizeof(uint32_t));
+ }
+ else
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_set_samplerate(airspyhf_device_t* device, uint32_t samplerate)
+{
+ int result;
+ uint32_t i;
+ uint8_t gain;
+
+ if (samplerate > MAX_SAMPLERATE_INDEX)
+ {
+ for (i = 0; i < device->supported_samplerate_count; i++)
+ {
+ if (samplerate == device->supported_samplerates[i])
+ {
+ samplerate = i;
+ break;
+ }
+ }
+ }
+
+ if (samplerate >= device->supported_samplerate_count)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ device->current_samplerate = device->supported_samplerates[samplerate];
+ device->is_low_if = device->samplerate_architectures[samplerate];
+
+ libusb_clear_halt(device->usb_device, LIBUSB_ENDPOINT_IN | 1);
+
+ if (!device->is_low_if && device->freq_khz < MIN_ZERO_IF_LO)
+ {
+ uint8_t buf[4];
+ buf[0] = (uint8_t)((MIN_ZERO_IF_LO >> 24) & 0xff);
+ buf[1] = (uint8_t)((MIN_ZERO_IF_LO >> 16) & 0xff);
+ buf[2] = (uint8_t)((MIN_ZERO_IF_LO >> 8) & 0xff);
+ buf[3] = (uint8_t)((MIN_ZERO_IF_LO) & 0xff);
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_SET_FREQ,
+ 0,
+ 0,
+ (unsigned char*) &buf,
+ sizeof(buf),
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < sizeof(buf))
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ device->freq_khz = MIN_ZERO_IF_LO;
+ }
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_SET_SAMPLERATE,
+ 0,
+ samplerate,
+ NULL,
+ 0,
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result != 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_GET_FILTER_GAIN,
+ 0,
+ 0,
+ &gain,
+ sizeof(uint8_t),
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result == sizeof(uint8_t))
+ {
+ device->filter_gain = powf(10.0f, (float) gain * -0.05f);
+ }
+ else
+ {
+ device->filter_gain = 1.0;
+ }
+
+ airspyhf_set_freq_double(device, device->freq_hz);
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_set_receiver_mode(airspyhf_device_t* device, receiver_mode_t value)
+{
+ int result;
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_RECEIVER_MODE,
+ value,
+ 0,
+ NULL,
+ 0,
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result != 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_start(airspyhf_device_t* device, airspyhf_sample_block_cb_fn callback, void* ctx)
+{
+ int result;
+
+ memset(device->dropped_buffers_queue, 0, RAW_BUFFER_COUNT * sizeof(uint32_t));
+ device->dropped_buffers = 0;
+
+ device->vec.re = 1.0f;
+ device->vec.im = 0.0f;
+
+ result = airspyhf_set_receiver_mode(device, RECEIVER_MODE_OFF);
+ if (result != AIRSPYHF_SUCCESS)
+ {
+ return result;
+ }
+
+ libusb_clear_halt(device->usb_device, LIBUSB_ENDPOINT_IN | AIRSPYHF_ENDPOINT_IN);
+
+ result = airspyhf_set_receiver_mode(device, RECEIVER_MODE_ON);
+ if (result == AIRSPYHF_SUCCESS)
+ {
+ device->ctx = ctx;
+ result = create_io_threads(device, callback);
+ }
+
+ return result;
+}
+
+int ADDCALL airspyhf_is_streaming(airspyhf_device_t* device)
+{
+ return device->streaming && !device->stop_requested;
+}
+
+int ADDCALL airspyhf_stop(airspyhf_device_t* device)
+{
+ int result1, result2;
+ device->stop_requested = true;
+ result1 = airspyhf_set_receiver_mode(device, RECEIVER_MODE_OFF);
+ result2 = kill_io_threads(device);
+
+#ifndef _WIN32
+ libusb_interrupt_event_handler(device->usb_context);
+#endif
+
+ if (result2 != AIRSPYHF_SUCCESS)
+ {
+ return result2;
+ }
+ return result1;
+}
+
+
+int ADDCALL airspyhf_set_freq(airspyhf_device_t* device, const uint32_t freq_hz)
+{
+ return airspyhf_set_freq_double(device, freq_hz);
+}
+
+int ADDCALL airspyhf_set_freq_double(airspyhf_device_t* device, const double freq_hz)
+{
+ int result;
+ uint8_t buf[4];
+ double if_shift = (device->enable_dsp && !device->is_low_if) ? DEFAULT_IF_SHIFT : 0;
+ double adjusted_freq_hz = freq_hz * (1.0e9 + device->calibration_ppb) * 1.0e-9;
+ uint32_t lo_low_khz = device->is_low_if ? MIN_LOW_IF_LO : MIN_ZERO_IF_LO;
+ uint32_t freq_khz = MAX(lo_low_khz, (uint32_t)round((adjusted_freq_hz + if_shift) * 1e-3));
+
+ if (device->freq_khz != freq_khz)
+ {
+ device->iq_balancer_eval_skip = IQ_BALANCER_EVAL_SKIP;
+
+ buf[0] = (uint8_t)((freq_khz >> 24) & 0xff);
+ buf[1] = (uint8_t)((freq_khz >> 16) & 0xff);
+ buf[2] = (uint8_t)((freq_khz >> 8) & 0xff);
+ buf[3] = (uint8_t)((freq_khz) & 0xff);
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_SET_FREQ,
+ 0,
+ 0,
+ (unsigned char*)&buf,
+ sizeof(buf),
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < sizeof(buf))
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ device->freq_khz = freq_khz;
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_GET_FREQ_DELTA,
+ 0,
+ 0,
+ (unsigned char*)&buf,
+ sizeof(buf),
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result == sizeof(buf))
+ {
+ device->freq_delta_hz = (((int8_t)buf[3] << 16) | (buf[2] << 8) | buf[1]) * 1e3 / (1 << buf[0]);
+ }
+
+ iq_balancer_set_optimal_point(device->iq_balancer, device->optimal_point);
+ }
+
+ device->freq_hz = freq_hz;
+ device->freq_shift = adjusted_freq_hz - freq_khz * 1e3 + device->freq_delta_hz;
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_get_frontend_options(airspyhf_device_t* device, uint32_t* flags)
+{
+ if (flags)
+ {
+ *flags = device->frontend_options;
+ return AIRSPYHF_SUCCESS;
+ }
+
+ return AIRSPYHF_ERROR;
+}
+
+int ADDCALL airspyhf_set_frontend_options(airspyhf_device_t* device, uint32_t flags)
+{
+ int result;
+
+ device->frontend_options = flags;
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_SET_FRONTEND_OPTIONS,
+ (uint16_t)(flags & 0xffff),
+ (uint16_t)(flags >> 16),
+ NULL,
+ 0,
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+ return AIRSPYHF_SUCCESS;
+}
+
+static int airspyhf_config_write(airspyhf_device_t* device, uint8_t *buffer, uint16_t length)
+{
+ int result;
+ uint8_t buf[256];
+
+ memset(buf, 0, sizeof(buf));
+
+ memcpy(buf, buffer, MIN(length, sizeof(buf)));
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_CONFIG_WRITE,
+ 0,
+ 0,
+ buf,
+ sizeof(buf),
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < sizeof(buf))
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+static int airspyhf_config_read(airspyhf_device_t* device, uint8_t *buffer, uint16_t length)
+{
+ uint8_t buf[256];
+ int result;
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_CONFIG_READ,
+ 0,
+ 0,
+ buf,
+ sizeof(buf),
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ memcpy(buffer, buf, MIN(length, sizeof(buf)));
+
+ if (result < sizeof(buf))
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_get_calibration(airspyhf_device_t* device, int32_t* ppb)
+{
+ if (ppb)
+ {
+ *ppb = device->calibration_ppb;
+ return AIRSPYHF_SUCCESS;
+ }
+
+ return AIRSPYHF_ERROR;
+}
+
+int ADDCALL airspyhf_set_calibration(airspyhf_device_t* device, int32_t ppb)
+{
+ device->calibration_ppb = ppb;
+ return airspyhf_set_freq_double(device, device->freq_hz);
+}
+
+int ADDCALL airspyhf_get_vctcxo_calibration(airspyhf_device_t* device, uint16_t* vc)
+{
+ if (vc)
+ {
+ *vc = device->calibration_vctcxo;
+ return AIRSPYHF_SUCCESS;
+ }
+
+ return AIRSPYHF_ERROR;
+}
+
+int ADDCALL airspyhf_set_vctcxo_calibration(airspyhf_device_t* device, uint16_t vc)
+{
+ int result;
+ device->calibration_vctcxo = vc;
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_SET_VCTCXO_CALIBRATION,
+ vc,
+ 0,
+ NULL,
+ 0,
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_flash_configuration(airspyhf_device_t* device)
+{
+ flash_config_t config;
+ config.magic_number = CALIBRATION_MAGIC;
+ config.calibration_ppb = device->calibration_ppb;
+ config.calibration_vctcxo = device->calibration_vctcxo;
+ config.frontend_options = device->frontend_options;
+
+ if (airspyhf_is_streaming(device))
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ if (airspyhf_config_write(device, (uint8_t *)&config, sizeof(config)) != AIRSPYHF_SUCCESS)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_set_optimal_iq_correction_point(airspyhf_device_t* device, float w)
+{
+ device->optimal_point = w;
+ iq_balancer_set_optimal_point(device->iq_balancer, device->optimal_point);
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_iq_balancer_configure(airspyhf_device_t* device, int buffers_to_skip, int fft_integration, int fft_overlap, int correlation_integration)
+{
+ iq_balancer_configure(device->iq_balancer, buffers_to_skip, fft_integration, fft_overlap, correlation_integration);
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_set_lib_dsp(airspyhf_device_t* device, const uint8_t flag)
+{
+ device->enable_dsp = flag;
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_board_partid_serialno_read(airspyhf_device_t* device, airspyhf_read_partid_serialno_t* read_partid_serialno)
+{
+ uint8_t length;
+ int result;
+
+ length = sizeof(airspyhf_read_partid_serialno_t);
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_GET_SERIALNO_BOARDID,
+ 0,
+ 0,
+ (unsigned char*)read_partid_serialno,
+ length,
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < length)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_version_string_read(airspyhf_device_t* device, char* version, uint8_t length)
+{
+ int result;
+ char version_local[MAX_VERSION_STRING_SIZE];
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_GET_VERSION_STRING,
+ 0,
+ 0,
+ (unsigned char*) version_local,
+ (MAX_VERSION_STRING_SIZE - 1),
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+ else
+ {
+ if (length > 0)
+ {
+ memcpy(version, version_local, length - 1);
+ version[length - 1] = 0;
+ return AIRSPYHF_SUCCESS;
+ }
+ else
+ {
+ return AIRSPYHF_ERROR;
+ }
+ }
+}
+
+int ADDCALL airspyhf_get_att_steps(airspyhf_device_t* device, void* buffer, const uint32_t len)
+{
+ if (len == 0)
+ {
+ *(uint32_t*)buffer = device->supported_att_step_count;
+ }
+ else if (len <= device->supported_att_step_count)
+ {
+ memcpy(buffer, device->supported_att_steps, len * sizeof(float));
+ }
+ else
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_set_att(airspyhf_device_t* device, float att)
+{
+ int result;
+ uint16_t att_index = 0;
+
+ for (uint32_t i = 0; i < device->supported_att_step_count; i++)
+ {
+ if (device->supported_att_steps[i] >= att)
+ {
+ att_index = i;
+ break;
+ }
+ }
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_SET_ATT,
+ att_index,
+ 0,
+ NULL,
+ 0,
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_set_bias_tee(airspyhf_device_t* device, int8_t value)
+{
+ int result;
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_SET_BIAS_TEE,
+ (uint16_t)value,
+ 0,
+ NULL,
+ 0,
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_get_bias_tee_count(airspyhf_device_t* device, int32_t* value)
+{
+ uint8_t length;
+ int result;
+
+ length = sizeof(airspyhf_read_partid_serialno_t);
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_GET_BIAS_TEE_COUNT,
+ 0,
+ 0,
+ (unsigned char*)value,
+ length,
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < length)
+ {
+ *value = 0;
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_get_bias_tee_name(airspyhf_device_t* device, int32_t index, char* version, uint8_t length)
+{
+ int result;
+ char name_local[MAX_NAME_STRING_SIZE];
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_GET_BIAS_TEE_NAME,
+ 0,
+ (int16_t)index,
+ (unsigned char*)name_local,
+ (MAX_NAME_STRING_SIZE - 1),
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+ else
+ {
+ if (length > 0)
+ {
+ memcpy(version, name_local, length - 1);
+ version[length - 1] = 0;
+ return AIRSPYHF_SUCCESS;
+ }
+ else
+ {
+ return AIRSPYHF_ERROR;
+ }
+ }
+}
+
+// Legacy for HF+ dual and HF+ Discovery
+
+int ADDCALL airspyhf_set_hf_att(airspyhf_device_t* device, uint8_t att_index)
+{
+ int result;
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_SET_ATT,
+ (uint16_t)att_index,
+ 0,
+ NULL,
+ 0,
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_set_hf_lna(airspyhf_device_t* device, uint8_t flag)
+{
+ int result;
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_SET_LNA,
+ (uint16_t) flag,
+ 0,
+ NULL,
+ 0,
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_set_user_output(airspyhf_device_t* device, airspyhf_user_output_t pin, airspyhf_user_output_state_t value)
+{
+ int result;
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_SET_USER_OUTPUT,
+ (uint16_t)pin,
+ (uint16_t)value,
+ NULL,
+ 0,
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_set_hf_agc(airspyhf_device_t* device, uint8_t flag)
+{
+ int result;
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_SET_AGC,
+ (uint16_t)flag,
+ 0,
+ NULL,
+ 0,
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_set_hf_agc_threshold(airspyhf_device_t* device, uint8_t flag)
+{
+ int result;
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_SET_AGC_THRESHOLD,
+ (uint16_t)flag,
+ 0,
+ NULL,
+ 0,
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
diff --git a/Radio/HW/AirSpyHF/src/airspyhf.h b/Radio/HW/AirSpyHF/src/airspyhf.h
new file mode 100644
index 0000000..2879120
--- /dev/null
+++ b/Radio/HW/AirSpyHF/src/airspyhf.h
@@ -0,0 +1,171 @@
+/*
+Copyright (c) 2013-2024, Youssef Touil <youssef@airspy.com>
+Copyright (c) 2013-2017, Ian Gilmour <ian@sdrsharp.com>
+Copyright (c) 2013-2017, Benjamin Vernoux <bvernoux@airspy.com>
+Copyright (c) 2013, Michael Ossmann <mike@ossmann.com>
+Copyright (c) 2012, Jared Boone <jared@sharebrained.com>
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+ Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ Neither the name of Airspy HF+ nor the names of its contributors may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef __AIRSPYHF_H__
+#define __AIRSPYHF_H__
+
+#include <stdint.h>
+
+#define AIRSPYHF_VERSION "1.8.0"
+#define AIRSPYHF_VER_MAJOR 1
+#define AIRSPYHF_VER_MINOR 8
+#define AIRSPYHF_VER_REVISION 0
+
+#define AIRSPYHF_ENDPOINT_IN (1)
+
+#if defined(_WIN32) && !defined(STATIC_AIRSPYHFPLUS)
+ #define ADD_EXPORTS
+
+ /* You should define ADD_EXPORTS *only* when building the DLL. */
+ #ifdef ADD_EXPORTS
+ #define ADDAPI __declspec(dllexport)
+ #else
+ #define ADDAPI __declspec(dllimport)
+ #endif
+
+ /* Define calling convention in one place, for convenience. */
+ #define ADDCALL __cdecl
+
+#else /* _WIN32 not defined. */
+
+ /* Define with no value on non-Windows OSes. */
+ #define ADDAPI
+ #define ADDCALL
+
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+enum airspyhf_error
+{
+ AIRSPYHF_SUCCESS = 0,
+ AIRSPYHF_ERROR = -1,
+ AIRSPYHF_UNSUPPORTED = -2
+};
+
+typedef struct {
+ float re;
+ float im;
+} airspyhf_complex_float_t;
+
+typedef struct {
+ uint32_t part_id;
+ uint32_t serial_no[4];
+} airspyhf_read_partid_serialno_t;
+
+enum airspyhf_board_id
+{
+ AIRSPYHF_BOARD_ID_UNKNOWN_AIRSPYHF = 0,
+ AIRSPYHF_BOARD_ID_AIRSPYHF_REV_A = 1,
+ AIRSPYHF_BOARD_ID_AIRSPYHF_DISCOVERY_REV_A = 2,
+ AIRSPYHF_BOARD_ID_INVALID = 0xFF,
+};
+
+typedef struct airspyhf_device airspyhf_device_t;
+
+typedef struct {
+ airspyhf_device_t* device;
+ void* ctx;
+ airspyhf_complex_float_t* samples;
+ int sample_count;
+ uint64_t dropped_samples;
+} airspyhf_transfer_t;
+
+typedef struct {
+ uint32_t major_version;
+ uint32_t minor_version;
+ uint32_t revision;
+} airspyhf_lib_version_t;
+
+#define MAX_NAME_STRING_SIZE (64)
+#define MAX_VERSION_STRING_SIZE MAX_NAME_STRING_SIZE
+
+typedef int (*airspyhf_sample_block_cb_fn) (airspyhf_transfer_t* transfer_fn);
+
+extern ADDAPI void ADDCALL airspyhf_lib_version(airspyhf_lib_version_t* lib_version);
+extern ADDAPI int ADDCALL airspyhf_list_devices(uint64_t *serials, int count);
+extern ADDAPI int ADDCALL airspyhf_open(airspyhf_device_t** device);
+extern ADDAPI int ADDCALL airspyhf_open_sn(airspyhf_device_t** device, uint64_t serial_number);
+extern ADDAPI int ADDCALL airspyhf_open_fd(airspyhf_device_t** device, int fd);
+extern ADDAPI int ADDCALL airspyhf_close(airspyhf_device_t* device);
+extern ADDAPI int ADDCALL airspyhf_get_output_size(airspyhf_device_t* device); /* Returns the number of IQ samples to expect in the callback */
+extern ADDAPI int ADDCALL airspyhf_start(airspyhf_device_t* device, airspyhf_sample_block_cb_fn callback, void* ctx);
+extern ADDAPI int ADDCALL airspyhf_stop(airspyhf_device_t* device);
+extern ADDAPI int ADDCALL airspyhf_is_streaming(airspyhf_device_t* device);
+extern ADDAPI int ADDCALL airspyhf_is_low_if(airspyhf_device_t* device); /* Tells if the current sample rate is Zero-IF (0) or Low-IF (1) */
+extern ADDAPI int ADDCALL airspyhf_set_freq(airspyhf_device_t* device, const uint32_t freq_hz);
+extern ADDAPI int ADDCALL airspyhf_set_freq_double(airspyhf_device_t* device, const double freq_hz);
+extern ADDAPI int ADDCALL airspyhf_set_lib_dsp(airspyhf_device_t* device, const uint8_t flag); /* Enables/Disables the IQ Correction, IF shift and Fine Tuning. */
+extern ADDAPI int ADDCALL airspyhf_get_samplerates(airspyhf_device_t* device, uint32_t* buffer, const uint32_t len);
+extern ADDAPI int ADDCALL airspyhf_set_samplerate(airspyhf_device_t* device, uint32_t samplerate);
+extern ADDAPI int ADDCALL airspyhf_set_att(airspyhf_device_t* device, float value);
+extern ADDAPI int ADDCALL airspyhf_get_att_steps(airspyhf_device_t* device, void* buffer, const uint32_t len);
+extern ADDAPI int ADDCALL airspyhf_set_bias_tee(airspyhf_device_t* device, int8_t value);
+extern ADDAPI int ADDCALL airspyhf_get_bias_tee_count(airspyhf_device_t* device, int32_t* count);
+extern ADDAPI int ADDCALL airspyhf_get_bias_tee_name(airspyhf_device_t* device, int32_t index, char* version, uint8_t length);
+extern ADDAPI int ADDCALL airspyhf_get_calibration(airspyhf_device_t* device, int32_t* ppb);
+extern ADDAPI int ADDCALL airspyhf_set_calibration(airspyhf_device_t* device, int32_t ppb);
+extern ADDAPI int ADDCALL airspyhf_get_vctcxo_calibration(airspyhf_device_t* device, uint16_t* vc);
+extern ADDAPI int ADDCALL airspyhf_set_vctcxo_calibration(airspyhf_device_t* device, uint16_t vc);
+extern ADDAPI int ADDCALL airspyhf_get_frontend_options(airspyhf_device_t* device, uint32_t* flags);
+extern ADDAPI int ADDCALL airspyhf_set_frontend_options(airspyhf_device_t* device, uint32_t flags);
+extern ADDAPI int ADDCALL airspyhf_set_optimal_iq_correction_point(airspyhf_device_t* device, float w);
+extern ADDAPI int ADDCALL airspyhf_iq_balancer_configure(airspyhf_device_t* device, int buffers_to_skip, int fft_integration, int fft_overlap, int correlation_integration);
+extern ADDAPI int ADDCALL airspyhf_flash_configuration(airspyhf_device_t* device); /* streaming needs to be stopped */
+extern ADDAPI int ADDCALL airspyhf_board_partid_serialno_read(airspyhf_device_t* device, airspyhf_read_partid_serialno_t* read_partid_serialno);
+extern ADDAPI int ADDCALL airspyhf_version_string_read(airspyhf_device_t* device, char* version, uint8_t length);
+
+// Legacy stuff for backward compatibility
+
+typedef enum
+{
+ AIRSPYHF_USER_OUTPUT_0 = 0,
+ AIRSPYHF_USER_OUTPUT_1 = 1,
+ AIRSPYHF_USER_OUTPUT_2 = 2,
+ AIRSPYHF_USER_OUTPUT_3 = 3
+} airspyhf_user_output_t;
+
+typedef enum
+{
+ AIRSPYHF_USER_OUTPUT_LOW = 0,
+ AIRSPYHF_USER_OUTPUT_HIGH = 1
+} airspyhf_user_output_state_t;
+
+#define airspyhf_flash_calibration airspyhf_flash_configuration
+
+extern ADDAPI int ADDCALL airspyhf_set_user_output(airspyhf_device_t* device, airspyhf_user_output_t pin, airspyhf_user_output_state_t value);
+extern ADDAPI int ADDCALL airspyhf_set_hf_agc(airspyhf_device_t* device, uint8_t flag); /* 0 = off, 1 = on */
+extern ADDAPI int ADDCALL airspyhf_set_hf_agc_threshold(airspyhf_device_t* device, uint8_t flag); /* when agc on: 0 = low, 1 = high */
+extern ADDAPI int ADDCALL airspyhf_set_hf_att(airspyhf_device_t* device, uint8_t att_index); /* Possible values: 0..8 Range: 0..48 dB Attenuation with 6 dB steps */
+extern ADDAPI int ADDCALL airspyhf_set_hf_lna(airspyhf_device_t* device, uint8_t flag); /* 0 or 1: 1 to activate LNA (alias PreAmp): 1 = +6 dB gain - compensated in digital */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif//__AIRSPYHF_H__
diff --git a/Radio/HW/AirSpyHF/src/airspyhf_commands.h b/Radio/HW/AirSpyHF/src/airspyhf_commands.h
new file mode 100644
index 0000000..db19690
--- /dev/null
+++ b/Radio/HW/AirSpyHF/src/airspyhf_commands.h
@@ -0,0 +1,63 @@
+/*
+Copyright (c) 2013-2023, Youssef Touil <youssef@airspy.com>
+Copyright (c) 2013-2017, Ian Gilmour <ian@sdrsharp.com>
+Copyright (c) 2013-2017, Benjamin Vernoux <bvernoux@airspy.com>
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+Neither the name of Airspy HF+ nor the names of its contributors may be used to endorse or promote products derived from this software
+without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef __AIRSPYHF_COMMANDS_H__
+#define __AIRSPYHF_COMMANDS_H__
+
+#include <stdint.h>
+
+typedef enum
+{
+ RECEIVER_MODE_OFF = 0,
+ RECEIVER_MODE_ON = 1
+} receiver_mode_t;
+
+#define AIRSPYHF_CMD_MAX (22)
+typedef enum
+{
+ AIRSPYHF_INVALID = 0,
+ AIRSPYHF_RECEIVER_MODE = 1,
+ AIRSPYHF_SET_FREQ = 2,
+ AIRSPYHF_GET_SAMPLERATES = 3,
+ AIRSPYHF_SET_SAMPLERATE = 4,
+ AIRSPYHF_CONFIG_READ = 5,
+ AIRSPYHF_CONFIG_WRITE = 6,
+ AIRSPYHF_GET_SERIALNO_BOARDID = 7,
+ AIRSPYHF_SET_USER_OUTPUT = 8,
+ AIRSPYHF_GET_VERSION_STRING = 9,
+ AIRSPYHF_SET_AGC = 10,
+ AIRSPYHF_SET_AGC_THRESHOLD = 11,
+ AIRSPYHF_SET_ATT = 12,
+ AIRSPYHF_SET_LNA = 13,
+ AIRSPYHF_GET_SAMPLERATE_ARCHITECTURES = 14,
+ AIRSPYHF_GET_FILTER_GAIN = 15,
+ AIRSPYHF_GET_FREQ_DELTA = 16,
+ AIRSPYHF_SET_VCTCXO_CALIBRATION = 17,
+ AIRSPYHF_SET_FRONTEND_OPTIONS = 18,
+ AIRSPYHF_GET_ATT_STEPS = 19,
+ AIRSPYHF_GET_BIAS_TEE_COUNT = 20,
+ AIRSPYHF_GET_BIAS_TEE_NAME = 21,
+ AIRSPYHF_SET_BIAS_TEE = AIRSPYHF_CMD_MAX
+} airspyhf_vendor_request;
+
+#endif
diff --git a/Radio/HW/AirSpyHF/src/iqbalancer.c b/Radio/HW/AirSpyHF/src/iqbalancer.c
new file mode 100644
index 0000000..2bf5901
--- /dev/null
+++ b/Radio/HW/AirSpyHF/src/iqbalancer.c
@@ -0,0 +1,564 @@
+/*
+Copyright (c) 2016-2023, Youssef Touil <youssef@airspy.com>
+Copyright (c) 2018, Leif Asbrink <leif@sm5bsz.com>
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+ Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ Neither the name of Airspy HF+ nor the names of its contributors may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+#include "iqbalancer.h"
+
+#ifndef MATH_PI
+#define MATH_PI 3.14159265359
+#endif
+
+#define EPSILON 0.01f
+#define WorkingBufferLength (FFTBins * (1 + FFTIntegration / FFTOverlap))
+
+struct iq_balancer_t
+{
+ float phase;
+ float last_phase;
+
+ float amplitude;
+ float last_amplitude;
+
+ float iavg;
+ float qavg;
+ float integrated_total_power;
+ float integrated_image_power;
+ float maximum_image_power;
+
+ float raw_phases[MaxLookback];
+ float raw_amplitudes[MaxLookback];
+
+ int skipped_buffers;
+ int buffers_to_skip;
+ int working_buffer_pos;
+ int fft_integration;
+ int fft_overlap;
+ int correlation_integration;
+
+ int no_of_avg;
+ int no_of_raw;
+ int raw_ptr;
+ int optimal_bin;
+ int reset_flag;
+ int *power_flag;
+
+ complex_t *corr;
+ complex_t *corr_plus;
+ complex_t *working_buffer;
+ float *boost;
+};
+
+static uint8_t __lib_initialized = 0;
+static float __fft_window[FFTBins];
+static float __boost_window[FFTBins];
+
+static void __init_library()
+{
+ int i;
+
+ if (__lib_initialized)
+ {
+ return;
+ }
+
+ const int length = FFTBins - 1;
+
+ for (i = 0; i <= length; i++)
+ {
+ __fft_window[i] = (float)(
+ +0.35875f
+ - 0.48829f * cos(2.0 * MATH_PI * i / length)
+ + 0.14128f * cos(4.0 * MATH_PI * i / length)
+ - 0.01168f * cos(6.0 * MATH_PI * i / length)
+ );
+ __boost_window[i] = (float)(1.0 / BoostFactor + 1.0 / exp(pow(i * 2.0 / BinsToOptimize, 2.0)));
+ }
+
+ __lib_initialized = 1;
+}
+
+static void window(complex_t *buffer, int length)
+{
+ int i;
+ for (i = 0; i < length; i++)
+ {
+ buffer[i].re *= __fft_window[i];
+ buffer[i].im *= __fft_window[i];
+ }
+}
+
+static void fft(complex_t *buffer, int length)
+{
+ int nm1 = length - 1;
+ int nd2 = length / 2;
+ int i, j, jm1, k, l, m, le, le2, ip;
+ complex_t u, t, r;
+
+ m = 0;
+ i = length;
+ while (i > 1)
+ {
+ ++m;
+ i = (i >> 1);
+ }
+
+ j = nd2;
+
+ for (i = 1; i < nm1; ++i)
+ {
+ if (i < j)
+ {
+ t = buffer[j];
+ buffer[j] = buffer[i];
+ buffer[i] = t;
+ }
+
+ k = nd2;
+
+ while (k <= j)
+ {
+ j = j - k;
+ k = k / 2;
+ }
+
+ j += k;
+ }
+
+ for (l = 1; l <= m; ++l)
+ {
+ le = 1 << l;
+ le2 = le / 2;
+
+ u.re = 1.0f;
+ u.im = 0.0f;
+
+ r.re = (float)cos(MATH_PI / le2);
+ r.im = (float)-sin(MATH_PI / le2);
+
+ for (j = 1; j <= le2; ++j)
+ {
+ jm1 = j - 1;
+
+ for (i = jm1; i <= nm1; i += le)
+ {
+ ip = i + le2;
+
+ t.re = u.re * buffer[ip].re - u.im * buffer[ip].im;
+ t.im = u.im * buffer[ip].re + u.re * buffer[ip].im;
+
+ buffer[ip].re = buffer[i].re - t.re;
+ buffer[ip].im = buffer[i].im - t.im;
+
+ buffer[i].re += t.re;
+ buffer[i].im += t.im;
+ }
+
+ t.re = u.re * r.re - u.im * r.im;
+ t.im = u.im * r.re + u.re * r.im;
+
+ u.re = t.re;
+ u.im = t.im;
+ }
+ }
+
+ for (i = 0; i < nd2; i++)
+ {
+ j = nd2 + i;
+ t = buffer[i];
+ buffer[i] = buffer[j];
+ buffer[j] = t;
+ }
+}
+
+static void cancel_dc(struct iq_balancer_t *iq_balancer, complex_t* iq, int length, uint8_t skip_eval)
+{
+ int i;
+ float iavg = iq_balancer->iavg;
+ float qavg = iq_balancer->qavg;
+
+ if (skip_eval)
+ {
+ for (i = 0; i < length; i++)
+ {
+ iq[i].re -= iavg;
+ iq[i].im -= qavg;
+ }
+ }
+ else
+ {
+ for (i = 0; i < length; i++)
+ {
+ iavg += DcTimeConst * (iq[i].re - iavg);
+ qavg += DcTimeConst * (iq[i].im - qavg);
+
+ iq[i].re -= iavg;
+ iq[i].im -= qavg;
+ }
+ iq_balancer->iavg = iavg;
+ iq_balancer->qavg = qavg;
+ }
+}
+
+static float adjust_benchmark(struct iq_balancer_t *iq_balancer, complex_t *iq, float phase, float amplitude, int skip_power_calculation)
+{
+ int i;
+ float sum = 0;
+ for (i = 0; i < FFTBins; i++)
+ {
+ float re = iq[i].re;
+ float im = iq[i].im;
+
+ iq[i].re += phase * im;
+ iq[i].im += phase * re;
+
+ iq[i].re *= 1 + amplitude;
+ iq[i].im *= 1 - amplitude;
+ if (!skip_power_calculation)
+ sum += re * re + im * im;
+ }
+ return sum;
+}
+
+static complex_t multiply_complex_complex(complex_t *a, const complex_t *b)
+{
+ complex_t result;
+ result.re = a->re * b->re - a->im * b->im;
+ result.im = a->im * b->re + a->re * b->im;
+ return result;
+}
+
+static int compute_corr(struct iq_balancer_t *iq_balancer, complex_t* iq, complex_t* ccorr, int length, int step)
+{
+ complex_t cc, fftPtr[FFTBins];
+ int n, m;
+ int i, j;
+ int count = 0;
+ float power;
+ float phase = iq_balancer->phase + step * PhaseStep;
+ float amplitude = iq_balancer->amplitude + step * AmplitudeStep;
+
+ for (n = 0, m = 0; n <= length - FFTBins && m < iq_balancer->fft_integration; n += FFTBins / iq_balancer->fft_overlap, m++)
+ {
+ memcpy(fftPtr, iq + n, FFTBins * sizeof(complex_t));
+ power = adjust_benchmark(iq_balancer, fftPtr, phase, amplitude, step);
+ if (step == 0)
+ {
+ if (power > MinimumPower)
+ {
+ iq_balancer->power_flag[m] = 1;
+ iq_balancer->integrated_total_power += power;
+ }
+ else
+ {
+ iq_balancer->power_flag[m] = 0;
+ }
+ }
+ if (iq_balancer->power_flag[m] == 1)
+ {
+ count++;
+ window(fftPtr, FFTBins);
+ fft(fftPtr, FFTBins);
+ for (i = EdgeBinsToSkip, j = FFTBins - EdgeBinsToSkip; i <= FFTBins - EdgeBinsToSkip; i++, j--)
+ {
+ cc = multiply_complex_complex(fftPtr + i, fftPtr + j);
+ ccorr[i].re += cc.re;
+ ccorr[i].im += cc.im;
+
+ ccorr[j].re = ccorr[i].re;
+ ccorr[j].im = ccorr[i].im;
+ }
+ if (step == 0)
+ {
+ for (i = EdgeBinsToSkip; i <= FFTBins - EdgeBinsToSkip; i++)
+ {
+ power = fftPtr[i].re * fftPtr[i].re + fftPtr[i].im * fftPtr[i].im;
+ iq_balancer->boost[i] += power;
+ if (iq_balancer->optimal_bin == FFTBins / 2)
+ {
+ iq_balancer->integrated_image_power += power;
+ }
+ else
+ {
+ iq_balancer->integrated_image_power += power * __boost_window[abs(FFTBins - i - iq_balancer->optimal_bin)];
+ }
+ }
+ }
+ }
+ }
+
+ return count;
+}
+
+static complex_t utility(struct iq_balancer_t *iq_balancer, complex_t* ccorr)
+{
+ int i;
+ int j;
+ float invskip = 1.0f / EdgeBinsToSkip;
+ complex_t acc = { 0, 0 };
+ for (i = EdgeBinsToSkip, j = FFTBins - EdgeBinsToSkip; i <= FFTBins - EdgeBinsToSkip; i++, j--)
+ {
+ int distance = abs(i - FFTBins / 2);
+ if (distance > CenterBinsToSkip)
+ {
+ float weight = (distance > EdgeBinsToSkip) ? 1.0f : (distance * invskip);
+ if (iq_balancer->optimal_bin != FFTBins / 2)
+ {
+ weight *= __boost_window[abs(iq_balancer->optimal_bin - i)];
+ }
+ weight *= iq_balancer->boost[j] / (iq_balancer->boost[i] + EPSILON);
+ acc.re += ccorr[i].re * weight;
+ acc.im += ccorr[i].im * weight;
+ }
+ }
+ return acc;
+}
+
+static void estimate_imbalance(struct iq_balancer_t *iq_balancer, complex_t* iq, int length)
+{
+ int i, j;
+ float amplitude, phase, mu;
+ complex_t a, b;
+
+ if (iq_balancer->reset_flag)
+ {
+ iq_balancer->reset_flag = 0;
+ iq_balancer->no_of_avg = -BuffersToSkipOnReset;
+ iq_balancer->maximum_image_power = 0;
+ }
+
+ if (iq_balancer->no_of_avg < 0)
+ {
+ iq_balancer->no_of_avg++;
+ return;
+ }
+ else if (iq_balancer->no_of_avg == 0)
+ {
+ iq_balancer->integrated_image_power = 0;
+ iq_balancer->integrated_total_power = 0;
+ memset(iq_balancer->boost, 0, FFTBins * sizeof(float));
+ memset(iq_balancer->corr, 0, FFTBins * sizeof(complex_t));
+ memset(iq_balancer->corr_plus, 0, FFTBins * sizeof(complex_t));
+ }
+
+ iq_balancer->maximum_image_power *= MaxPowerDecay;
+
+ i = compute_corr(iq_balancer, iq, iq_balancer->corr, length, 0);
+ if (i == 0)
+ return;
+
+ iq_balancer->no_of_avg += i;
+ compute_corr(iq_balancer, iq, iq_balancer->corr_plus, length, 1);
+
+ if (iq_balancer->no_of_avg <= iq_balancer->correlation_integration * iq_balancer->fft_integration)
+ return;
+
+ iq_balancer->no_of_avg = 0;
+
+ if (iq_balancer->optimal_bin == FFTBins / 2)
+ {
+ if (iq_balancer->integrated_total_power < iq_balancer->maximum_image_power)
+ return;
+ iq_balancer->maximum_image_power = iq_balancer->integrated_total_power;
+ }
+ else
+ {
+ if (iq_balancer->integrated_image_power - iq_balancer->integrated_total_power * BoostWindowNorm < iq_balancer->maximum_image_power * PowerThreshold)
+ return;
+ iq_balancer->maximum_image_power = iq_balancer->integrated_image_power - iq_balancer->integrated_total_power * BoostWindowNorm;
+ }
+
+ a = utility(iq_balancer, iq_balancer->corr);
+ b = utility(iq_balancer, iq_balancer->corr_plus);
+
+ mu = a.im - b.im;
+ if (fabs(mu) > MinDeltaMu)
+ {
+ mu = a.im / mu;
+ if (mu < -MaxMu)
+ mu = -MaxMu;
+ else if (mu > MaxMu)
+ mu = MaxMu;
+ }
+ else
+ {
+ mu = 0;
+ }
+
+ phase = iq_balancer->phase + PhaseStep * mu;
+
+ mu = a.re - b.re;
+ if (fabs(mu) > MinDeltaMu)
+ {
+ mu = a.re / mu;
+ if (mu < -MaxMu)
+ mu = -MaxMu;
+ else if (mu > MaxMu)
+ mu = MaxMu;
+ }
+ else
+ {
+ mu = 0;
+ }
+
+ amplitude = iq_balancer->amplitude + AmplitudeStep * mu;
+
+ if (iq_balancer->no_of_raw < MaxLookback)
+ iq_balancer->no_of_raw++;
+ iq_balancer->raw_amplitudes[iq_balancer->raw_ptr] = amplitude;
+ iq_balancer->raw_phases[iq_balancer->raw_ptr] = phase;
+ i = iq_balancer->raw_ptr;
+ for (j = 0; j < iq_balancer->no_of_raw - 1; j++)
+ {
+ i = (i + MaxLookback - 1) & (MaxLookback - 1);
+ phase += iq_balancer->raw_phases[i];
+ amplitude += iq_balancer->raw_amplitudes[i];
+ }
+ phase /= iq_balancer->no_of_raw;
+ amplitude /= iq_balancer->no_of_raw;
+ iq_balancer->raw_ptr = (iq_balancer->raw_ptr + 1) & (MaxLookback - 1);
+
+ iq_balancer->phase = phase;
+ iq_balancer->amplitude = amplitude;
+}
+
+static void adjust_phase_amplitude(struct iq_balancer_t *iq_balancer, complex_t* iq, int length)
+{
+ int i;
+ float scale = 1.0f / (length - 1);
+
+ for (i = 0; i < length; i++)
+ {
+ float phase = (i * iq_balancer->last_phase + (length - 1 - i) * iq_balancer->phase) * scale;
+ float amplitude = (i * iq_balancer->last_amplitude + (length - 1 - i) * iq_balancer->amplitude) * scale;
+
+ float re = iq[i].re;
+ float im = iq[i].im;
+
+ iq[i].re += phase * im;
+ iq[i].im += phase * re;
+
+ iq[i].re *= 1 + amplitude;
+ iq[i].im *= 1 - amplitude;
+ }
+
+ iq_balancer->last_phase = iq_balancer->phase;
+ iq_balancer->last_amplitude = iq_balancer->amplitude;
+}
+
+void ADDCALL iq_balancer_process(struct iq_balancer_t *iq_balancer, complex_t* iq, int length, uint8_t skip_eval)
+{
+ int count;
+
+ cancel_dc(iq_balancer, iq, length, skip_eval);
+
+ if (!skip_eval)
+ {
+ count = WorkingBufferLength - iq_balancer->working_buffer_pos;
+ if (count >= length)
+ {
+ count = length;
+ }
+ memcpy(iq_balancer->working_buffer + iq_balancer->working_buffer_pos, iq, count * sizeof(complex_t));
+ iq_balancer->working_buffer_pos += count;
+ if (iq_balancer->working_buffer_pos >= WorkingBufferLength)
+ {
+ iq_balancer->working_buffer_pos = 0;
+
+ if (++iq_balancer->skipped_buffers > iq_balancer->buffers_to_skip)
+ {
+ iq_balancer->skipped_buffers = 0;
+ estimate_imbalance(iq_balancer, iq_balancer->working_buffer, WorkingBufferLength);
+ }
+ }
+ }
+
+ adjust_phase_amplitude(iq_balancer, iq, length);
+}
+
+void ADDCALL iq_balancer_set_optimal_point(struct iq_balancer_t *iq_balancer, float w)
+{
+ if (w < -0.5f)
+ {
+ w = -0.5f;
+ }
+ else if (w > 0.5f)
+ {
+ w = 0.5f;
+ }
+
+ iq_balancer->optimal_bin = (int)floor(FFTBins * (0.5 + w));
+ iq_balancer->reset_flag = 1;
+}
+
+void ADDCALL iq_balancer_configure(struct iq_balancer_t *iq_balancer, int buffers_to_skip, int fft_integration, int fft_overlap, int correlation_integration)
+{
+ iq_balancer->buffers_to_skip = buffers_to_skip;
+ iq_balancer->fft_integration = fft_integration;
+ iq_balancer->fft_overlap = fft_overlap;
+ iq_balancer->correlation_integration = correlation_integration;
+
+ free(iq_balancer->power_flag);
+ iq_balancer->power_flag = (int *)malloc(iq_balancer->fft_integration * sizeof(int));
+ memset(iq_balancer->power_flag, 0, iq_balancer->fft_integration * sizeof(int));
+
+ iq_balancer->reset_flag = 1;
+}
+
+struct iq_balancer_t * ADDCALL iq_balancer_create(float initial_phase, float initial_amplitude)
+{
+ struct iq_balancer_t *instance = (struct iq_balancer_t *) malloc(sizeof(struct iq_balancer_t));
+ memset(instance, 0, sizeof(struct iq_balancer_t));
+
+ instance->phase = initial_phase;
+ instance->amplitude = initial_amplitude;
+
+ instance->optimal_bin = FFTBins / 2;
+
+ instance->buffers_to_skip = BuffersToSkip;
+ instance->fft_integration = FFTIntegration;
+ instance->fft_overlap = FFTOverlap;
+ instance->correlation_integration = CorrelationIntegration;
+
+ instance->corr = (complex_t *)malloc(FFTBins * sizeof(complex_t));
+ instance->corr_plus = (complex_t *)malloc(FFTBins * sizeof(complex_t));
+ instance->working_buffer = (complex_t *)malloc(WorkingBufferLength * sizeof(complex_t));
+ instance->boost = (float *)malloc(FFTBins * sizeof(float));
+ instance->power_flag = (int *)malloc(instance->fft_integration * sizeof(int));
+
+ __init_library();
+ return instance;
+}
+
+void ADDCALL iq_balancer_destroy(struct iq_balancer_t *iq_balancer)
+{
+ free(iq_balancer->corr);
+ free(iq_balancer->corr_plus);
+ free(iq_balancer->working_buffer);
+ free(iq_balancer->boost);
+ free(iq_balancer->power_flag);
+ free(iq_balancer);
+}
diff --git a/Radio/HW/AirSpyHF/src/iqbalancer.h b/Radio/HW/AirSpyHF/src/iqbalancer.h
new file mode 100644
index 0000000..baa1374
--- /dev/null
+++ b/Radio/HW/AirSpyHF/src/iqbalancer.h
@@ -0,0 +1,68 @@
+/*
+Copyright (c) 2016-2023, Youssef Touil <youssef@airspy.com>
+Copyright (c) 2018, Leif Asbrink <leif@sm5bsz.com>
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+ Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ Neither the name of Airspy HF+ nor the names of its contributors may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef __IQ_BALANCER_H__
+#define __IQ_BALANCER_H__
+
+#include "airspyhf.h"
+
+#define FFTBins (4 * 1024)
+#define BoostFactor 100000.0
+#define BinsToOptimize (FFTBins/25)
+#define EdgeBinsToSkip (FFTBins/22)
+#define CenterBinsToSkip 2
+#define MaxLookback 4
+#define PhaseStep 1e-2f
+#define AmplitudeStep 1e-2f
+#define MaxMu 50.0f
+#define MinDeltaMu 0.1f
+#define DcTimeConst 1e-4f
+#define MinimumPower 0.01f
+#define PowerThreshold 0.5f
+#define BuffersToSkipOnReset 2
+#define MaxPowerDecay 0.98f
+#define MaxPowerRatio 0.8f
+#define BoostWindowNorm (MaxPowerRatio / 95)
+
+#if defined(__arm__) && !defined(__force_hiq__)
+ #define BuffersToSkip 4
+ #define FFTIntegration 2
+ #define FFTOverlap 1
+ #define CorrelationIntegration 4
+#else
+ #define BuffersToSkip 2
+ #define FFTIntegration 4
+ #define FFTOverlap 2
+ #define CorrelationIntegration 16
+#endif
+
+struct iq_balancer_t;
+
+typedef airspyhf_complex_float_t complex_t;
+
+ADDAPI struct iq_balancer_t * ADDCALL iq_balancer_create(float initial_phase, float initial_amplitude);
+ADDAPI void ADDCALL iq_balancer_set_optimal_point(struct iq_balancer_t *iq_balancer, float w);
+ADDAPI void ADDCALL iq_balancer_configure(struct iq_balancer_t *iq_balancer, int buffers_to_skip, int fft_integration, int fft_overlap, int correlation_integration);
+ADDAPI void ADDCALL iq_balancer_process(struct iq_balancer_t *iq_balancer, complex_t* iq, int length, uint8_t skip_eval);
+ADDAPI void ADDCALL iq_balancer_destroy(struct iq_balancer_t *iq_balancer);
+
+#endif