2022-01-06 13:16:06 +01:00
|
|
|
#include "constellation.h"
|
|
|
|
|
#include <cmath>
|
2022-12-22 13:08:09 +01:00
|
|
|
// #include <iostream>
|
2022-01-06 13:16:06 +01:00
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
#define M_SQRT2 1.41421356237309504880
|
2022-01-07 10:26:54 +01:00
|
|
|
#ifndef M_PI
|
|
|
|
|
#define M_PI 3.14159265358979323846 /* pi */
|
|
|
|
|
#endif
|
2022-01-06 13:16:06 +01:00
|
|
|
|
2022-03-31 11:25:15 +02:00
|
|
|
#include "logger.h"
|
2024-03-14 12:12:34 +01:00
|
|
|
#include "core/exception.h"
|
2022-03-31 11:25:15 +02:00
|
|
|
|
2022-01-06 13:16:06 +01:00
|
|
|
namespace dsp
|
|
|
|
|
{
|
|
|
|
|
complex_t constellation_t::polar(float r, int n, float i)
|
|
|
|
|
{
|
|
|
|
|
float a = i * 2 * M_PI / n;
|
|
|
|
|
return complex_t(r * cosf(a), r * sinf(a));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
constellation_t::constellation_t(constellation_type_t type, float g1, float g2) : const_type(type)
|
|
|
|
|
{
|
|
|
|
|
if (type == BPSK)
|
|
|
|
|
{
|
|
|
|
|
const_states = 2;
|
|
|
|
|
const_bits = 1;
|
|
|
|
|
|
|
|
|
|
constellation = new complex_t[const_states];
|
|
|
|
|
|
|
|
|
|
constellation[0] = complex_t(-1, 0);
|
|
|
|
|
constellation[1] = complex_t(1, 0);
|
|
|
|
|
}
|
2022-12-22 13:08:09 +01:00
|
|
|
else if (type == QPSK || type == OQPSK) // Distinction is NOT at constellation level
|
2022-01-06 13:16:06 +01:00
|
|
|
{
|
|
|
|
|
const_states = 4;
|
|
|
|
|
const_bits = 2;
|
|
|
|
|
const_amp = 3;
|
|
|
|
|
|
|
|
|
|
constellation = new complex_t[const_states];
|
|
|
|
|
|
|
|
|
|
// Default constellation, Gray-Coded
|
|
|
|
|
constellation[0] = complex_t(-M_SQRT2, -M_SQRT2);
|
|
|
|
|
constellation[1] = complex_t(M_SQRT2, -M_SQRT2);
|
|
|
|
|
constellation[2] = complex_t(-M_SQRT2, M_SQRT2);
|
|
|
|
|
constellation[3] = complex_t(M_SQRT2, M_SQRT2);
|
|
|
|
|
}
|
|
|
|
|
else if (type == PSK8)
|
|
|
|
|
{
|
|
|
|
|
const_states = 8;
|
|
|
|
|
const_bits = 3;
|
|
|
|
|
|
|
|
|
|
constellation = new complex_t[const_states];
|
|
|
|
|
|
|
|
|
|
// Gray-coded
|
|
|
|
|
float rcp_sqrt_2 = 0.70710678118654752440;
|
|
|
|
|
constellation[0] = complex_t(0.0, -1.0);
|
|
|
|
|
constellation[1] = complex_t(-rcp_sqrt_2, rcp_sqrt_2);
|
|
|
|
|
constellation[2] = complex_t(rcp_sqrt_2, -rcp_sqrt_2);
|
|
|
|
|
constellation[3] = complex_t(0.0, 1.0);
|
|
|
|
|
constellation[4] = complex_t(-rcp_sqrt_2, -rcp_sqrt_2);
|
|
|
|
|
constellation[5] = complex_t(-1.0, 0.0);
|
|
|
|
|
constellation[6] = complex_t(1.0, 0.0);
|
|
|
|
|
constellation[7] = complex_t(rcp_sqrt_2, rcp_sqrt_2);
|
|
|
|
|
}
|
|
|
|
|
else if (type == APSK16)
|
|
|
|
|
{
|
|
|
|
|
const_states = 16;
|
|
|
|
|
const_bits = 4;
|
|
|
|
|
const_amp = 100;
|
2022-06-02 12:20:28 +02:00
|
|
|
const_sca = 1; // 0.5;
|
|
|
|
|
const_prescale = 0.53;
|
2022-01-06 13:16:06 +01:00
|
|
|
|
|
|
|
|
constellation = new complex_t[const_states];
|
|
|
|
|
|
|
|
|
|
float gamma1 = g1;
|
|
|
|
|
|
|
|
|
|
if (!gamma1)
|
|
|
|
|
gamma1 = 2.57;
|
|
|
|
|
|
|
|
|
|
float r1 = sqrtf(4 / (1 + 3 * gamma1 * gamma1));
|
|
|
|
|
float r2 = gamma1 * r1;
|
|
|
|
|
r1 *= 0.5;
|
|
|
|
|
r2 *= 0.5;
|
|
|
|
|
|
|
|
|
|
constellation[15] = polar(r2, 12, 1.5) * const_amp;
|
|
|
|
|
constellation[14] = polar(r2, 12, 10.5) * const_amp;
|
|
|
|
|
constellation[13] = polar(r2, 12, 4.5) * const_amp;
|
|
|
|
|
constellation[12] = polar(r2, 12, 7.5) * const_amp;
|
|
|
|
|
constellation[11] = polar(r2, 12, 0.5) * const_amp;
|
|
|
|
|
constellation[10] = polar(r2, 12, 11.5) * const_amp;
|
|
|
|
|
constellation[9] = polar(r2, 12, 5.5) * const_amp;
|
|
|
|
|
constellation[8] = polar(r2, 12, 6.5) * const_amp;
|
|
|
|
|
constellation[7] = polar(r2, 12, 2.5) * const_amp;
|
|
|
|
|
constellation[6] = polar(r2, 12, 9.5) * const_amp;
|
|
|
|
|
constellation[5] = polar(r2, 12, 3.5) * const_amp;
|
|
|
|
|
constellation[4] = polar(r2, 12, 8.5) * const_amp;
|
|
|
|
|
constellation[3] = polar(r1, 4, 0.5) * const_amp;
|
|
|
|
|
constellation[2] = polar(r1, 4, 3.5) * const_amp;
|
|
|
|
|
constellation[1] = polar(r1, 4, 1.5) * const_amp;
|
|
|
|
|
constellation[0] = polar(r1, 4, 2.5) * const_amp;
|
|
|
|
|
}
|
|
|
|
|
else if (type == APSK32)
|
|
|
|
|
{
|
|
|
|
|
const_states = 32;
|
|
|
|
|
const_bits = 5;
|
|
|
|
|
const_amp = 100;
|
2022-06-02 12:20:28 +02:00
|
|
|
const_sca = 1; // 0.5;
|
|
|
|
|
const_prescale = 0.54;
|
2022-01-06 13:16:06 +01:00
|
|
|
|
|
|
|
|
constellation = new complex_t[const_states];
|
|
|
|
|
|
|
|
|
|
float gamma1 = g1;
|
|
|
|
|
float gamma2 = g2;
|
|
|
|
|
|
|
|
|
|
if (!gamma1)
|
|
|
|
|
gamma1 = 2.53;
|
|
|
|
|
if (!gamma2)
|
|
|
|
|
gamma2 = 4.30;
|
|
|
|
|
|
|
|
|
|
float r1 = sqrtf(8 / (1 + 3 * gamma1 * gamma1 + 4 * gamma2 * gamma2));
|
|
|
|
|
float r2 = gamma1 * r1;
|
|
|
|
|
float r3 = gamma2 * r1;
|
|
|
|
|
r1 *= 0.5;
|
|
|
|
|
r2 *= 0.5;
|
|
|
|
|
r3 *= 0.5;
|
|
|
|
|
|
|
|
|
|
constellation[31] = polar(r2, 12, 1.5) * const_amp;
|
|
|
|
|
constellation[30] = polar(r2, 12, 2.5) * const_amp;
|
|
|
|
|
constellation[29] = polar(r2, 12, 10.5) * const_amp;
|
|
|
|
|
constellation[28] = polar(r2, 12, 9.5) * const_amp;
|
|
|
|
|
constellation[27] = polar(r2, 12, 4.5) * const_amp;
|
|
|
|
|
constellation[26] = polar(r2, 12, 3.5) * const_amp;
|
|
|
|
|
constellation[25] = polar(r2, 12, 7.5) * const_amp;
|
|
|
|
|
constellation[24] = polar(r2, 12, 8.5) * const_amp;
|
|
|
|
|
constellation[23] = polar(r3, 16, 1) * const_amp;
|
|
|
|
|
constellation[22] = polar(r3, 16, 3) * const_amp;
|
|
|
|
|
constellation[21] = polar(r3, 16, 14) * const_amp;
|
|
|
|
|
constellation[20] = polar(r3, 16, 12) * const_amp;
|
|
|
|
|
constellation[19] = polar(r3, 16, 6) * const_amp;
|
|
|
|
|
constellation[18] = polar(r3, 16, 4) * const_amp;
|
|
|
|
|
constellation[17] = polar(r3, 16, 9) * const_amp;
|
|
|
|
|
constellation[16] = polar(r3, 16, 11) * const_amp;
|
|
|
|
|
constellation[15] = polar(r2, 12, 0.5) * const_amp;
|
|
|
|
|
constellation[14] = polar(r1, 4, 0.5) * const_amp;
|
|
|
|
|
constellation[13] = polar(r2, 12, 11.5) * const_amp;
|
|
|
|
|
constellation[12] = polar(r1, 4, 3.5) * const_amp;
|
|
|
|
|
constellation[11] = polar(r2, 12, 5.5) * const_amp;
|
|
|
|
|
constellation[10] = polar(r1, 4, 1.5) * const_amp;
|
|
|
|
|
constellation[9] = polar(r2, 12, 6.5) * const_amp;
|
|
|
|
|
constellation[8] = polar(r1, 4, 2.5) * const_amp;
|
|
|
|
|
constellation[7] = polar(r3, 16, 0) * const_amp;
|
|
|
|
|
constellation[6] = polar(r3, 16, 2) * const_amp;
|
|
|
|
|
constellation[5] = polar(r3, 16, 15) * const_amp;
|
|
|
|
|
constellation[4] = polar(r3, 16, 13) * const_amp;
|
|
|
|
|
constellation[3] = polar(r3, 16, 7) * const_amp;
|
|
|
|
|
constellation[2] = polar(r3, 16, 5) * const_amp;
|
|
|
|
|
constellation[1] = polar(r3, 16, 8) * const_amp;
|
|
|
|
|
constellation[0] = polar(r3, 16, 10) * const_amp;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2024-03-14 12:12:34 +01:00
|
|
|
throw satdump_exception("Undefined constellation type!");
|
2022-01-06 13:16:06 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
constellation_t::~constellation_t()
|
|
|
|
|
{
|
|
|
|
|
delete[] constellation;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
complex_t constellation_t::mod(uint8_t symbol)
|
|
|
|
|
{
|
2022-03-31 11:25:15 +02:00
|
|
|
return (constellation[symbol] / const_amp) / const_prescale;
|
2022-01-06 13:16:06 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
uint8_t constellation_t::demod(complex_t sample)
|
|
|
|
|
{
|
|
|
|
|
switch (const_type)
|
|
|
|
|
{
|
|
|
|
|
case BPSK:
|
|
|
|
|
return sample.real > 0;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case QPSK:
|
|
|
|
|
return 2 * (sample.imag > 0) + (sample.real > 0);
|
|
|
|
|
break;
|
|
|
|
|
|
2022-12-22 13:08:09 +01:00
|
|
|
case OQPSK:
|
|
|
|
|
return 2 * (sample.imag > 0) + (sample.real > 0);
|
|
|
|
|
break;
|
|
|
|
|
|
2022-01-06 13:16:06 +01:00
|
|
|
default:
|
|
|
|
|
return 0;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
uint8_t constellation_t::soft_demod(int8_t *sample)
|
|
|
|
|
{
|
|
|
|
|
switch (const_type)
|
|
|
|
|
{
|
|
|
|
|
case BPSK:
|
|
|
|
|
return sample[0] > 0;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case QPSK:
|
|
|
|
|
return 2 * (sample[1] > 0) + (sample[0] > 0);
|
|
|
|
|
break;
|
|
|
|
|
|
2022-12-22 13:08:09 +01:00
|
|
|
case OQPSK:
|
|
|
|
|
return 2 * (sample[1] > 0) + (sample[0] > 0);
|
|
|
|
|
break;
|
|
|
|
|
|
2022-01-06 13:16:06 +01:00
|
|
|
default:
|
|
|
|
|
return 0;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2022-01-07 18:23:28 +01:00
|
|
|
void constellation_t::soft_demod(int8_t *samples, int size, uint8_t *bits)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < size / 2; i++)
|
|
|
|
|
bits[i] = soft_demod(&samples[i * 2]);
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-06 13:16:06 +01:00
|
|
|
void constellation_t::demod_soft_calc(complex_t sample, int8_t *bits, float *phase_error, float npwr)
|
|
|
|
|
{
|
|
|
|
|
int v;
|
2022-03-31 11:25:15 +02:00
|
|
|
std::vector<float> tmp(2 * const_bits, 0);
|
2022-01-06 13:16:06 +01:00
|
|
|
|
|
|
|
|
if (const_amp != 1)
|
|
|
|
|
sample = sample * const_amp;
|
2022-03-31 11:25:15 +02:00
|
|
|
if (const_prescale != 1)
|
|
|
|
|
sample = sample * const_prescale;
|
2022-01-06 13:16:06 +01:00
|
|
|
|
|
|
|
|
float min_dist = std::numeric_limits<float>::max();
|
|
|
|
|
complex_t closest = 0;
|
2022-05-12 18:19:01 +02:00
|
|
|
// int c_i = 0;
|
|
|
|
|
|
2022-03-31 11:25:15 +02:00
|
|
|
for (int i = 0; i < const_states; i++)
|
2022-01-06 13:16:06 +01:00
|
|
|
{
|
|
|
|
|
// Calculate the distance between the sample and the current
|
|
|
|
|
// constellation point.
|
|
|
|
|
float dist = std::abs(std::complex<float>(sample - constellation[i]));
|
|
|
|
|
|
|
|
|
|
if (dist < min_dist)
|
|
|
|
|
{
|
|
|
|
|
min_dist = dist;
|
|
|
|
|
closest = constellation[i];
|
2022-05-12 18:19:01 +02:00
|
|
|
// c_i = i;
|
2022-01-06 13:16:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate the probability factor from the distance and
|
|
|
|
|
// the scaled noise power.
|
|
|
|
|
float d = expf(-dist / npwr);
|
|
|
|
|
|
|
|
|
|
v = i;
|
|
|
|
|
|
2022-03-31 11:25:15 +02:00
|
|
|
for (int j = 0; j < const_bits; j++)
|
2022-01-06 13:16:06 +01:00
|
|
|
{
|
|
|
|
|
// Get the bit at the jth index
|
|
|
|
|
int mask = 1 << j;
|
|
|
|
|
int bit = (v & mask) >> j;
|
|
|
|
|
|
|
|
|
|
// If the bit is a 0, add to the probability of a zero
|
|
|
|
|
if (bit == 0)
|
|
|
|
|
tmp[2 * j + 0] += d;
|
|
|
|
|
// else, add to the probability of a one
|
|
|
|
|
else
|
|
|
|
|
tmp[2 * j + 1] += d;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-31 11:25:15 +02:00
|
|
|
// logger->info(c_i);
|
|
|
|
|
|
2022-01-06 13:16:06 +01:00
|
|
|
// Calculate the log-likelihood ratio for all bits based on the
|
|
|
|
|
// probability of ones (tmp[2*i+1]) over the probability of a zero
|
|
|
|
|
// (tmp[2*i+0]).
|
2022-03-28 11:51:13 +02:00
|
|
|
if (bits != nullptr)
|
2022-03-31 11:25:15 +02:00
|
|
|
for (int i = 0; i < const_bits; i++)
|
|
|
|
|
bits[const_bits - 1 - i] = clamp((logf(tmp[2 * i + 1]) - logf(tmp[2 * i + 0])) * const_sca);
|
2022-01-06 13:16:06 +01:00
|
|
|
|
|
|
|
|
// Calculate phase error
|
|
|
|
|
if (phase_error != nullptr)
|
2022-03-25 19:33:31 +01:00
|
|
|
*phase_error = (sample * closest.conj()).arg();
|
2022-01-06 13:16:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int8_t constellation_t::clamp(float x)
|
|
|
|
|
{
|
|
|
|
|
while (x < -127 || x > 127)
|
2022-01-11 14:20:25 +01:00
|
|
|
{
|
2022-01-06 13:16:06 +01:00
|
|
|
x *= 0.5;
|
2022-01-11 14:20:25 +01:00
|
|
|
if (!std::isfinite(x))
|
|
|
|
|
return x;
|
|
|
|
|
}
|
2022-01-06 13:16:06 +01:00
|
|
|
return x;
|
|
|
|
|
}
|
2022-03-28 11:51:13 +02:00
|
|
|
|
|
|
|
|
void constellation_t::make_lut(int resolution)
|
|
|
|
|
{
|
|
|
|
|
lut_resolution = resolution;
|
|
|
|
|
lut.resize(resolution);
|
|
|
|
|
|
|
|
|
|
for (int x = 0; x < resolution; x++)
|
|
|
|
|
{
|
|
|
|
|
lut[x].resize(resolution);
|
|
|
|
|
|
|
|
|
|
for (int y = 0; y < resolution; y++)
|
|
|
|
|
{
|
|
|
|
|
float x_v = (float(x - resolution / 2) / float(resolution)) * 1.5f;
|
|
|
|
|
float y_v = (float(y - resolution / 2) / float(resolution)) * 1.5f;
|
|
|
|
|
|
|
|
|
|
std::vector<int8_t> bits(const_bits);
|
|
|
|
|
float phase_err;
|
|
|
|
|
|
|
|
|
|
demod_soft_calc(complex_t(x_v, y_v), bits.data(), &phase_err);
|
|
|
|
|
|
|
|
|
|
lut[x][y] = {bits, phase_err};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void constellation_t::demod_soft_lut(complex_t sample, int8_t *bits, float *phase_error)
|
|
|
|
|
{
|
2022-06-02 12:20:28 +02:00
|
|
|
if (const_bits != 5)
|
|
|
|
|
{
|
|
|
|
|
int x = (sample.real / 1.5) * lut_resolution + lut_resolution / 2;
|
2022-03-28 11:51:13 +02:00
|
|
|
#if 1
|
2022-06-02 12:20:28 +02:00
|
|
|
if (x < 0)
|
|
|
|
|
x = 0;
|
|
|
|
|
if (x >= lut_resolution)
|
|
|
|
|
x = lut_resolution - 1;
|
2022-03-28 11:51:13 +02:00
|
|
|
#endif
|
|
|
|
|
|
2022-06-02 12:20:28 +02:00
|
|
|
int y = (sample.imag / 1.5) * lut_resolution + lut_resolution / 2;
|
2022-03-28 11:51:13 +02:00
|
|
|
#if 1
|
2022-06-02 12:20:28 +02:00
|
|
|
if (y < 0)
|
|
|
|
|
y = 0;
|
|
|
|
|
if (y >= lut_resolution)
|
|
|
|
|
y = lut_resolution - 1;
|
2022-03-28 11:51:13 +02:00
|
|
|
#endif
|
|
|
|
|
|
2022-06-02 12:20:28 +02:00
|
|
|
SoftResult &v = lut[x][y];
|
2022-03-28 11:51:13 +02:00
|
|
|
|
2022-06-02 12:20:28 +02:00
|
|
|
if (bits != nullptr)
|
|
|
|
|
for (int i = 0; i < const_bits; i++)
|
|
|
|
|
bits[i] = v.bits[i];
|
2022-03-28 11:51:13 +02:00
|
|
|
|
2022-06-02 12:20:28 +02:00
|
|
|
if (phase_error != nullptr)
|
|
|
|
|
*phase_error = v.phase_error;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
demod_soft_calc(sample, bits, phase_error);
|
|
|
|
|
}
|
2022-03-28 11:51:13 +02:00
|
|
|
}
|
2022-01-06 13:16:06 +01:00
|
|
|
}
|