summaryrefslogtreecommitdiff
path: root/Radio/HW/AirSpyHF/src/iqbalancer.c
diff options
context:
space:
mode:
Diffstat (limited to 'Radio/HW/AirSpyHF/src/iqbalancer.c')
-rw-r--r--Radio/HW/AirSpyHF/src/iqbalancer.c564
1 files changed, 564 insertions, 0 deletions
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);
+}