1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2025-05-23 18:52:28 -04:00

DATV demod: switched to work branch copy of leansdr

This commit is contained in:
f4exb 2019-03-17 21:31:42 +01:00
parent 7b9cb0e9fe
commit d4fe404dd6
31 changed files with 8844 additions and 2451 deletions

View File

@ -8,6 +8,11 @@ set(datv_SOURCES
datvdemodplugin.cpp
datvideostream.cpp
datvideorender.cpp
leansdr/dvb.cpp
leansdr/filtergen.cpp
leansdr/framework.cpp
leansdr/math.cpp
leansdr/sdr.cpp
)
set(datv_HEADERS
@ -16,6 +21,11 @@ set(datv_HEADERS
datvdemodplugin.h
datvideostream.h
datvideorender.h
leansdr/dvb.h
leansdr/filtergen.h
leansdr/framework.h
leansdr/math.h
leansdr/sdr.h
)
set(datv_FORMS

View File

@ -28,12 +28,87 @@ namespace leansdr {
static const int DEFAULT_GUI_DECIMATION = 64;
static inline cstln_lut<eucl_ss, 256> * make_dvbs2_constellation(cstln_lut<eucl_ss, 256>::predef c,
code_rate r)
{
float gamma1 = 1, gamma2 = 1, gamma3 = 1;
switch (c)
{
case cstln_lut<eucl_ss, 256>::APSK16:
// EN 302 307, section 5.4.3, Table 9
switch (r)
{
case FEC23:
case FEC46:
gamma1 = 3.15;
break;
case FEC34:
gamma1 = 2.85;
break;
case FEC45:
gamma1 = 2.75;
break;
case FEC56:
gamma1 = 2.70;
break;
case FEC89:
gamma1 = 2.60;
break;
case FEC910:
gamma1 = 2.57;
break;
default:
fail("cstln_lut<256>::make_dvbs2_constellation: Code rate not supported with APSK16");
return 0;
}
break;
case cstln_lut<eucl_ss, 256>::APSK32:
// EN 302 307, section 5.4.4, Table 10
switch (r)
{
case FEC34:
gamma1 = 2.84;
gamma2 = 5.27;
break;
case FEC45:
gamma1 = 2.72;
gamma2 = 4.87;
break;
case FEC56:
gamma1 = 2.64;
gamma2 = 4.64;
break;
case FEC89:
gamma1 = 2.54;
gamma2 = 4.33;
break;
case FEC910:
gamma1 = 2.53;
gamma2 = 4.30;
break;
default:
fail("cstln_lut<eucl_ss, 256>::make_dvbs2_constellation: Code rate not supported with APSK32");
return 0;
}
break;
case cstln_lut<eucl_ss, 256>::APSK64E:
// EN 302 307-2, section 5.4.5, Table 13f
gamma1 = 2.4;
gamma2 = 4.3;
gamma3 = 7;
break;
default:
break;
}
return new cstln_lut<eucl_ss, 256>(c, gamma1, gamma2, gamma3);
}
template<typename T> struct datvconstellation: runnable
{
T xymin, xymax;
unsigned long decimation;
unsigned long pixels_per_frame;
cstln_lut<256> **cstln; // Optional ptr to optional constellation
long pixels_per_frame;
cstln_lut<eucl_ss, 256> **cstln; // Optional ptr to optional constellation
TVScreen *m_objDATVScreen;
pipereader<complex<T> > in;
unsigned long phase;

View File

@ -531,34 +531,34 @@ void DATVDemod::InitDATVFramework()
switch(m_objRunning.enmModulation)
{
case BPSK:
m_objCfg.constellation = leansdr::cstln_lut<256>::BPSK;
m_objCfg.constellation = leansdr::cstln_lut<leansdr::eucl_ss, 256>::BPSK;
break;
case QPSK:
m_objCfg.constellation = leansdr::cstln_lut<256>::QPSK;
m_objCfg.constellation = leansdr::cstln_lut<leansdr::eucl_ss, 256>::QPSK;
break;
case PSK8:
m_objCfg.constellation = leansdr::cstln_lut<256>::PSK8;
m_objCfg.constellation = leansdr::cstln_lut<leansdr::eucl_ss, 256>::PSK8;
break;
case APSK16:
m_objCfg.constellation = leansdr::cstln_lut<256>::APSK16;
m_objCfg.constellation = leansdr::cstln_lut<leansdr::eucl_ss, 256>::APSK16;
break;
case APSK32:
m_objCfg.constellation = leansdr::cstln_lut<256>::APSK32;
m_objCfg.constellation = leansdr::cstln_lut<leansdr::eucl_ss, 256>::APSK32;
break;
case APSK64E:
m_objCfg.constellation = leansdr::cstln_lut<256>::APSK64E;
m_objCfg.constellation = leansdr::cstln_lut<leansdr::eucl_ss, 256>::APSK64E;
break;
case QAM16:
m_objCfg.constellation = leansdr::cstln_lut<256>::QAM16;
m_objCfg.constellation = leansdr::cstln_lut<leansdr::eucl_ss, 256>::QAM16;
break;
case QAM64:
m_objCfg.constellation = leansdr::cstln_lut<256>::QAM64;
m_objCfg.constellation = leansdr::cstln_lut<leansdr::eucl_ss, 256>::QAM64;
break;
case QAM256:
m_objCfg.constellation = leansdr::cstln_lut<256>::QAM256;
m_objCfg.constellation = leansdr::cstln_lut<leansdr::eucl_ss, 256>::QAM256;
break;
default:
m_objCfg.constellation = leansdr::cstln_lut<256>::BPSK;
m_objCfg.constellation = leansdr::cstln_lut<leansdr::eucl_ss, 256>::BPSK;
break;
}
@ -648,7 +648,7 @@ void DATVDemod::InitDATVFramework()
// Generic constellation receiver
p_symbols = new leansdr::pipebuf<leansdr::softsymbol>(m_objScheduler, "PSK soft-symbols", BUF_SYMBOLS);
p_symbols = new leansdr::pipebuf<leansdr::eucl_ss>(m_objScheduler, "PSK soft-symbols", BUF_SYMBOLS);
p_freq = new leansdr::pipebuf<leansdr::f32> (m_objScheduler, "freq", BUF_SLOW);
p_ss = new leansdr::pipebuf<leansdr::f32> (m_objScheduler, "SS", BUF_SLOW);
p_mer = new leansdr::pipebuf<leansdr::f32> (m_objScheduler, "MER", BUF_SLOW);
@ -682,7 +682,7 @@ void DATVDemod::InitDATVFramework()
return;
}
m_objDemodulator = new leansdr::cstln_receiver<leansdr::f32>(
m_objDemodulator = new leansdr::cstln_receiver<leansdr::f32, leansdr::eucl_ss>(
m_objScheduler,
sampler,
*p_preprocessed,
@ -694,8 +694,8 @@ void DATVDemod::InitDATVFramework()
if (m_objCfg.standard == DVB_S)
{
if ( m_objCfg.constellation != leansdr::cstln_lut<256>::QPSK
&& m_objCfg.constellation != leansdr::cstln_lut<256>::BPSK )
if ( m_objCfg.constellation != leansdr::cstln_lut<leansdr::eucl_ss, 256>::QPSK
&& m_objCfg.constellation != leansdr::cstln_lut<leansdr::eucl_ss, 256>::BPSK )
{
qWarning("DATVDemod::InitDATVFramework: non-standard constellation for DVB-S");
}

View File

@ -25,11 +25,7 @@ class DownChannelizer;
#define rfFilterFftLength 1024
#ifndef LEANSDR_FRAMEWORK
#define LEANSDR_FRAMEWORK
//LeanSDR
#include "leansdr/framework.h"
#include "leansdr/generic.h"
#include "leansdr/dsp.h"
@ -41,10 +37,10 @@ class DownChannelizer;
#include "leansdr/hdlc.h"
#include "leansdr/iess.h"
#endif
#include "datvconstellation.h"
#include "datvvideoplayer.h"
#include "datvideostream.h"
#include "datvideorender.h"
#include "channel/channelsinkapi.h"
#include "dsp/basebandsamplesink.h"
@ -60,9 +56,6 @@ class DownChannelizer;
#include "util/message.h"
#include "util/movingaverage.h"
#include "datvideostream.h"
#include "datvideorender.h"
#include <QMutex>
enum DATVModulation { BPSK, QPSK, PSK8, APSK16, APSK32, APSK64E, QAM16, QAM64, QAM256 };
@ -83,7 +76,7 @@ struct config
bool cnr; // Measure CNR
unsigned int decim; // Decimation, 0=auto
float Fm; // QPSK symbol rate (Hz)
leansdr::cstln_lut<256>::predef constellation;
leansdr::cstln_lut<leansdr::eucl_ss, 256>::predef constellation;
leansdr::code_rate fec;
float Ftune; // Bias frequency for the QPSK demodulator (Hz)
bool allow_drift;
@ -109,7 +102,7 @@ struct config
cnr(false),
decim(0),
Fm(2e6),
constellation(leansdr::cstln_lut<256>::QPSK),
constellation(leansdr::cstln_lut<leansdr::eucl_ss, 256>::QPSK),
fec(leansdr::FEC12),
Ftune(0),
allow_drift(false),
@ -323,7 +316,7 @@ private:
};
unsigned long m_lngExpectedReadIQ;
unsigned long m_lngReadIQ;
long m_lngReadIQ;
//************** LEANDBV Parameters **************
@ -372,7 +365,7 @@ private:
float *coeffs_sampler;
int ncoeffs_sampler;
leansdr::pipebuf<leansdr::softsymbol> *p_symbols;
leansdr::pipebuf<leansdr::eucl_ss> *p_symbols;
leansdr::pipebuf<leansdr::f32> *p_freq;
leansdr::pipebuf<leansdr::f32> *p_ss;
leansdr::pipebuf<leansdr::f32> *p_mer;
@ -386,7 +379,7 @@ private:
leansdr::file_writer<leansdr::cf32> *r_ppout;
//GENERIC CONSTELLATION RECEIVER
leansdr::cstln_receiver<leansdr::f32> *m_objDemodulator;
leansdr::cstln_receiver<leansdr::f32, leansdr::eucl_ss> *m_objDemodulator;
// DECONVOLUTION AND SYNCHRONIZATION
leansdr::pipebuf<leansdr::u8> *p_bytes;

View File

@ -21,7 +21,7 @@
#include <QMediaMetaData>
#include "datvdemodgui.h"
#include "datvideostream.h"
//#include "datvideostream.h"
#include "device/devicesourceapi.h"
#include "device/deviceuiset.h"

View File

@ -0,0 +1,250 @@
// This file is part of LeanSDR Copyright (C) 2016-2018 <pabr@pabr.org>.
// See the toplevel README for more information.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef LEANSDR_BCH_H
#define LEANSDR_BCH_H
#include "leansdr/discrmath.h"
namespace leansdr
{
// Interface to hide the template parameters
struct bch_interface
{
virtual void encode(const uint8_t *msg, size_t msgbytes, uint8_t *out) = 0;
virtual int decode(uint8_t *cw, size_t cwbytes) = 0;
}; // bch_interface
// BCH error correction.
// T: Unsigned type for packing binary polynomials.
// N: Number of parity bits.
// NP: Width of the polynomials supplied.
// DP: Actual degree of the minimum polynomials (all must be same).
// TGF: Unsigned type for syndromes (must be wider than DP).
// GFTRUNCGEN: Generator polynomial for GF(2^DP), with X^DP omitted.
template <typename T, int N, int NP, int DP, typename TGF, int GFTRUNCGEN>
struct bch_engine : bch_interface
{
bch_engine(const bitvect<T, NP> *polys, int _npolys)
: npolys(_npolys)
{
// Build the generator polynomial (product of polys[]).
g = 1;
for (int i = 0; i < npolys; ++i)
g = g * polys[i];
// Convert the polynomials to truncated representation
// (with X^DP omitted) for use with divmod().
truncpolys = new bitvect<T, DP>[npolys];
for (int i = 0; i < npolys; ++i)
truncpolys[i].copy(polys[i]);
// Check which polynomial contains each root.
// Note: The DVB-S2 polynomials are numbered so that
// syndpoly[2*i]==i, but we don't use that property.
syndpolys = new int[2 * npolys];
for (int i = 0; i < 2 * npolys; ++i)
{
int j;
for (j = 0; j < npolys; ++j)
if (!eval_poly(truncpolys[j], true, 1 + i))
break;
if (j == npolys)
fail("Bad polynomials/root");
syndpolys[i] = j;
}
}
// Generate BCH parity bits.
void encode(const uint8_t *msg, size_t msgbytes, uint8_t *out)
{
bitvect<T, N> parity = shiftdivmod(msg, msgbytes, g);
// Output as bytes, coefficient of highest degree first
for (int i = N / 8; i--; ++out)
*out = parity.v[i / sizeof(T)] >> ((i & (sizeof(T) - 1)) * 8);
}
// Decode BCH.
// Return number of bits corrected, or -1 on failure.
int decode(uint8_t *cw, size_t cwbytes)
{
//again:
bool corrupted = false;
// Divide by individual polynomials.
// TBD Maybe do in parallel, scanning cw only once.
bitvect<T, DP> rem[npolys];
for (int j = 0; j < npolys; ++j)
{
rem[j] = divmod(cw, cwbytes, truncpolys[j]);
}
// Compute syndromes.
TGF S[2 * npolys];
for (int i = 0; i < 2 * npolys; ++i)
{
// Compute R(alpha^(1+i)), exploiting the fact that
// R(x)=Q(x)g_j(X)+rem_j(X) and g_j(alpha^(1+i))=0
// for some j that we already determined.
// TBD Compute even exponents using conjugates.
S[i] = eval_poly(rem[syndpolys[i]], false, 1 + i);
if (S[i])
corrupted = true;
}
if (!corrupted)
return 0;
#if 0
fprintf(stderr, "synd:");
for ( int i=0; i<2*npolys; ++i ) fprintf(stderr, " %04x", S[i]);
fprintf(stderr, "\n");
#endif
// S_j = R(alpha_j) = 0+E(alpha_j) = sum(l=1..L)((alpha^j)^i_l)
// where i_1 .. i_L are the degrees of the non-zero coefficients of E.
// S_j = sum(l=1..L)((alpha^i_l)^j) = sum(l=1..L)(X_l^j)
// Berlekamp - Massey
// http://en.wikipedia.org/wiki/Berlekamp%E2%80%93Massey_algorithm
// TBD More efficient to work with logs of syndromes ?
int NN = 2 * npolys;
TGF C[NN] = {
1,
},
B[NN] = {
1,
};
int L = 0, m = 1;
TGF b = 1;
for (int n = 0; n < NN; ++n)
{
TGF d = S[n];
for (int i = 1; i <= L; ++i)
d = GF.add(d, GF.mul(C[i], S[n - i]));
if (d == 0)
++m;
else
{
TGF d_div_b = GF.mul(d, GF.inv(b));
if (2 * L <= n)
{
TGF tmp[NN];
memcpy(tmp, C, sizeof(tmp));
for (int i = 0; i < NN - m; ++i)
C[m + i] = GF.sub(C[m + i], GF.mul(d_div_b, B[i]));
L = n + 1 - L;
memcpy(B, tmp, sizeof(B));
b = d;
m = 1;
}
else
{
for (int i = 0; i < NN - m; ++i)
C[m + i] = GF.sub(C[m + i], GF.mul(d_div_b, B[i]));
++m;
}
}
}
// L is the number of errors.
// C of degree L is the error locator polynomial (Lambda).
// C(X) = sum(l=1..L)(1-X_l*X).
#if 0
fprintf(stderr, "C[%d]=", L);
for ( int i=0; i<NN; ++i ) fprintf(stderr, " %04x", C[i]);
fprintf(stderr, "\n");
#endif
// Forney
// http://en.wikipedia.org/wiki/Forney_algorithm
// Simplified because coefficients are in GF(2).
// Find zeroes of C by exhaustive search.
// TODO Chien method
int roots_found = 0;
for (int i = 0; i < (1 << DP) - 1; ++i)
{
// Candidate root ALPHA^i
TGF v = eval_poly(C, L, i);
if (!v)
{
// ALPHA^i is a root of C, i.e. the inverse of an X_l.
int loc = (i ? (1 << DP) - 1 - i : 0); // exponent of inverse
// Reverse because cw[0..cwbytes-1] is stored MSB first
int rloc = cwbytes * 8 - 1 - loc;
if (rloc < 0)
{
// This may happen if the code is used truncated.
return -1;
}
cw[rloc / 8] ^= 128 >> (rloc & 7);
++roots_found;
if (roots_found == L)
break;
}
}
if (roots_found != L)
return -1;
return L;
}
private:
// Eval a GF(2)[X] polynomial at a power of ALPHA.
TGF eval_poly(const bitvect<T, DP> &poly, bool is_trunc, int rootexp)
{
TGF acc = 0;
int re = 0;
for (int i = 0; i < DP; ++i)
{
if (poly[i])
acc = GF.add(acc, GF.exp(re));
re += rootexp;
if (re >= (1 << DP) - 1)
re -= (1 << DP) - 1; // mod 2^DP-1 incrementally
}
if (is_trunc)
acc = GF.add(acc, GF.exp(re));
return acc;
}
// Eval a GF(2^16)[X] polynomial at a power of ALPHA.
TGF eval_poly(const TGF *poly, int deg, int rootexp)
{
TGF acc = 0;
int re = 0;
for (int i = 0; i <= deg; ++i)
{
acc = GF.add(acc, GF.mul(poly[i], GF.exp(re)));
re += rootexp;
if (re >= (1 << DP) - 1)
re -= (1 << DP) - 1; // mod 2^DP-1 incrementally
}
return acc;
}
bitvect<T, DP> *truncpolys;
int npolys;
int *syndpolys;
bitvect<T, N> g;
// Finite group for syndrome calculations
gf2n<TGF, DP, 2, GFTRUNCGEN> GF;
}; // bch_engine
} // namespace leansdr
#endif // LEANSDR_BCH_H

View File

@ -1,6 +1,25 @@
// This file is part of LeanSDR Copyright (C) 2016-2018 <pabr@pabr.org>.
// See the toplevel README for more information.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef LEANSDR_CONVOLUTIONAL_H
#define LEANSDR_CONVOLUTIONAL_H
#include "framework.h"
#include "sdr.h"
namespace leansdr
{
@ -19,21 +38,12 @@ struct deconvol_poly
typedef u8 hardsymbol;
// Support soft of float input
inline u8 SYMVAL(const hardsymbol *s)
{
return *s;
}
inline u8 SYMVAL(const softsymbol *s)
{
return s->symbol;
}
inline u8 SYMVAL(const hardsymbol *s) { return *s; }
inline u8 SYMVAL(const softsymbol *s) { return s->symbol; }
typedef u8 decoded_byte;
deconvol_poly() :
hist(0)
{
}
deconvol_poly() : hist(0) {}
// Remap and deconvolve [nb*8] symbols into [nb] bytes.
// Return estimated number of bit errors.
@ -42,9 +52,11 @@ struct deconvol_poly
{
int nerrors = 0;
int halfway = nb / 2;
for (; nb--; ++pout)
{
decoded_byte byte = 0;
for (int bit = 8; bit--; ++pin)
{
hist = (hist << 2) | remap[SYMVAL(pin)];
@ -52,16 +64,17 @@ struct deconvol_poly
if (nb < halfway)
nerrors += parity(hist & POLY_ERRORS);
}
*pout = byte;
}
return nerrors;
}
private:
Thist hist;
};
// deconvol_poly
}; // deconvol_poly
// ALGEBRAIC DECONVOLUTION, OPTIMIZED
@ -72,41 +85,34 @@ private:
template <typename Tin, // Input IQ symbols
typename Thist, // Input shift registers (one for I, one for Q)
typename Tpoly, // Taps (interleaved IQIQIQ...)
Tpoly POLY_DECONVOL, Tpoly POLY_ERRORS>
Tpoly POLY_DECONVOL,
Tpoly POLY_ERRORS>
struct deconvol_poly2
{
typedef u8 hardsymbol;
// Support instanciation of template with soft of float input
inline u8 SYMVAL(const hardsymbol *s)
{
return *s;
}
inline u8 SYMVAL(const softsymbol *s)
{
return s->symbol;
}
inline u8 SYMVAL(const hardsymbol *s) { return *s; }
inline u8 SYMVAL(const softsymbol *s) { return s->symbol; }
typedef u8 decoded_byte;
deconvol_poly2() :
inI(0), inQ(0)
{
}
deconvol_poly2() : inI(0), inQ(0) {}
// Remap and deconvolve [nb*8] symbols into [nb] bytes.
// Return estimated number of bit errors or -1 if error.
// Return estimated number of bit errors.
int run(const Tin *pin, const u8 remap[], decoded_byte *pout, int nb)
{
if (nb & (sizeof(Thist) - 1)) {
fail("deconvol_poly2::run", "Must deconvolve sizeof(Thist) bytes at a time");
return -1;
fail("Must deconvolve sizeof(Thist) bytes at a time");
}
nb /= sizeof(Thist);
unsigned long nerrors = 0;
int halfway = nb / 2;
Thist histI = inI, histQ = inQ;
for (; nb--;)
{
// This is where we convolve bits in parallel.
@ -114,8 +120,7 @@ struct deconvol_poly2
Thist we = 0; // error bits (should be 0)
#if 0
// Trust gcc to unroll and evaluate the bit tests at compile-time.
for ( int bit=sizeof(Thist)*8; bit--; ++pin )
{
for ( int bit=sizeof(Thist)*8; bit--; ++pin ) {
u8 iq = remap[SYMVAL(pin)];
histI = (histI<<1) | (iq>>1);
histQ = (histQ<<1) | (iq&1);
@ -126,21 +131,26 @@ struct deconvol_poly2
}
#else
// Unroll manually.
#define LOOP(bit) { \
#define LOOP(bit) \
{ \
u8 iq = remap[SYMVAL(pin)]; \
histI = (histI << 1) | (iq >> 1); \
histQ = (histQ << 1) | (iq & 1); \
if ( POLY_DECONVOL & ((Tpoly)2<<(2*bit)) ) wd ^= histI; \
if ( POLY_DECONVOL & ((Tpoly)1<<(2*bit)) ) wd ^= histQ; \
if ( POLY_ERRORS & ((Tpoly)2<<(2*bit)) ) we ^= histI; \
if ( POLY_ERRORS & ((Tpoly)1<<(2*bit)) ) we ^= histQ; \
if (POLY_DECONVOL & ((Tpoly)2 << (2 * bit))) \
wd ^= histI; \
if (POLY_DECONVOL & ((Tpoly)1 << (2 * bit))) \
wd ^= histQ; \
if (POLY_ERRORS & ((Tpoly)2 << (2 * bit))) \
we ^= histI; \
if (POLY_ERRORS & ((Tpoly)1 << (2 * bit))) \
we ^= histQ; \
++pin; \
}
// Don't shift by more than the operand width
switch (sizeof(Thist) /* 8*/)
switch (sizeof(Thist) * 8)
{
#if 0 // Not needed yet - avoid compiler warnings
case 8:
case 64:
LOOP(63); LOOP(62); LOOP(61); LOOP(60);
LOOP(59); LOOP(58); LOOP(57); LOOP(56);
LOOP(55); LOOP(54); LOOP(53); LOOP(52);
@ -151,118 +161,85 @@ struct deconvol_poly2
LOOP(35); LOOP(34); LOOP(33); LOOP(32);
// Fall-through
#endif
case 4:
LOOP(31)
;
LOOP(30)
;
LOOP(29)
;
LOOP(28)
;
LOOP(27)
;
LOOP(26)
;
LOOP(25)
;
LOOP(24)
;
LOOP(23)
;
LOOP(22)
;
LOOP(21)
;
LOOP(20)
;
LOOP(19)
;
LOOP(18)
;
LOOP(17)
;
LOOP(16)
;
case 32:
LOOP(31);
LOOP(30);
LOOP(29);
LOOP(28);
LOOP(27);
LOOP(26);
LOOP(25);
LOOP(24);
LOOP(23);
LOOP(22);
LOOP(21);
LOOP(20);
LOOP(19);
LOOP(18);
LOOP(17);
LOOP(16);
// Fall-through
case 2:
LOOP(15)
;
LOOP(14)
;
LOOP(13)
;
LOOP(12)
;
LOOP(11)
;
LOOP(10)
;
LOOP(9)
;
LOOP(8)
;
case 16:
LOOP(15);
LOOP(14);
LOOP(13);
LOOP(12);
LOOP(11);
LOOP(10);
LOOP(9);
LOOP(8);
// Fall-through
case 1:
LOOP(7)
;
LOOP(6)
;
LOOP(5)
;
LOOP(4)
;
LOOP(3)
;
LOOP(2)
;
LOOP(1)
;
LOOP(0)
;
case 8:
LOOP(7);
LOOP(6);
LOOP(5);
LOOP(4);
LOOP(3);
LOOP(2);
LOOP(1);
LOOP(0);
break;
default:
fail("deconvol_poly2::run", "Thist not supported (1)");
return -1;
fail("Thist not supported");
}
#undef LOOP
#endif
switch (sizeof(Thist) /* 8*/)
switch (sizeof(Thist) * 8)
{
#if 0 // Not needed yet - avoid compiler warnings
case 8:
case 64:
*pout++ = wd >> 56;
*pout++ = wd >> 48;
*pout++ = wd >> 40;
*pout++ = wd >> 32;
// Fall-through
#endif
case 4:
case 32:
*pout++ = wd >> 24;
*pout++ = wd >> 16;
// Fall-through
case 2:
case 16:
*pout++ = wd >> 8;
// Fall-through
case 1:
case 8:
*pout++ = wd;
break;
default:
fail("deconvol_poly2::run", "Thist not supported (2)");
return -1;
fail("Thist not supported");
}
// Count errors when the shift registers are full
if (nb < halfway)
nerrors += hamming_weight(we);
}
inI = histI;
inQ = histQ;
return nerrors;
}
private:
Thist inI, inQ;
};
// deconvol_poly2
}; // deconvol_poly2
// CONVOLUTIONAL ENCODER
@ -274,18 +251,17 @@ struct convol_poly2
typedef u8 uncoded_byte;
typedef u8 hardsymbol;
convol_poly2() :
hist(0)
{
}
convol_poly2() : hist(0) {}
// Convolve [count] bytes into [count*8] symbols, and remap.
void run(const uncoded_byte *pin, const u8 remap[], hardsymbol *pout, int count)
void run(const uncoded_byte *pin, const u8 remap[],
hardsymbol *pout, int count)
{
for (; count--; ++pin)
{
uncoded_byte b = *pin;
for (int bit = 8; bit--; ++pout)
{
hist = (hist >> 1) | (((b >> bit) & 1) << 6);
@ -294,10 +270,10 @@ struct convol_poly2
}
}
}
private:
Thist hist;
};
// convol_poly2
}; // convol_poly2
// Generic BPSK..256QAM and puncturing
@ -309,26 +285,29 @@ struct convol_multipoly
int bits_in, bits_out, bps;
const Thist *polys; // [bits_out]
convol_multipoly() :
bits_in(0), bits_out(0), bps(0), polys(0), hist(0), nhist(0), sersymb(0), nsersymb(0)
convol_multipoly()
: bits_in(0), bits_out(0), bps(0),
hist(0), nhist(0), sersymb(0), nsersymb(0)
{
}
void encode(const uncoded_byte *pin, hardsymbol *pout, int count)
{
if (!bits_in || !bits_out || !bps)
{
fatal("leansdr::convol_multipoly::encode: convol_multipoly not configured");
return;
if (!bits_in || !bits_out || !bps) {
fatal("convol_multipoly not configured");
}
hardsymbol symbmask = (1 << bps) - 1;
for (; count--; ++pin)
{
uncoded_byte b = *pin;
for (int bit = 8; bit--;)
{
hist = (hist >> 1) | ((Thist)((b >> bit) & 1) << (HISTSIZE - 1));
++nhist;
if (nhist == bits_in)
{
for (int p = 0; p < bits_out; ++p)
@ -336,8 +315,10 @@ struct convol_multipoly
int b = parity((Thist)(hist & polys[p]));
sersymb = (sersymb << 1) | b;
}
nhist = 0;
nsersymb += bits_out;
while (nsersymb >= bps)
{
hardsymbol s = (sersymb >> (nsersymb - bps)) & symbmask;
@ -350,17 +331,17 @@ struct convol_multipoly
// Ensure deterministic output size
// TBD We can relax this
if (nhist || nsersymb) {
fatal("leansdr::convol_multipoly::encode: partial run");
fatal("partial run");
}
}
private:
Thist hist;
int nhist;
Thist sersymb;
int nsersymb;
};
// convol_multipoly
}; // convol_multipoly
}// namespace
} // namespace leansdr
#endif // LEANSDR_CONVOLUTIONAL_H

View File

@ -0,0 +1,73 @@
// This file is part of LeanSDR Copyright (C) 2016-2018 <pabr@pabr.org>.
// See the toplevel README for more information.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef LEANSDR_CRC_H
#define LEANSDR_CRC_H
#include <stdint.h>
#include "leansdr/discrmath.h"
namespace leansdr
{
// EN 302 307-1 section 5.1.4 CRC-8 encoder
struct crc8_engine
{
crc8_engine()
{
// Precompute
// EN 302 307-1 5.1.4 Figure 2
bitvect<uint8_t, 8> g = POLY_DVBS2_CRC8;
for (int u = 0; u < 256; ++u)
{
uint8_t u8 = u;
bitvect<uint8_t, 8> c = shiftdivmod(&u8, 1, g);
table[u] = c.v[0];
}
}
uint8_t compute(const uint8_t *buf, int len)
{
uint8_t c = 0;
for (; len--; ++buf)
c = table[c ^ *buf];
return c;
}
private:
static const uint8_t POLY_DVBS2_CRC8 = 0xd5; // (1)11010101
uint8_t table[256];
};
// CRC-32 ITU V.42 for FINGERPRINT
uint32_t crc32(const uint8_t *buf, int len)
{
static const uint32_t poly = 0xedb88320;
uint32_t c = 0xffffffff;
for (int i = 0; i < len; ++i)
{
c ^= buf[i];
for (int bit = 8; bit--;)
c = (c & 1) ? (c >> 1) ^ poly : (c >> 1);
}
return c ^ 0xffffffff;
}
} // namespace leansdr
#endif // LEANSDR_CRC_H

View File

@ -0,0 +1,268 @@
// This file is part of LeanSDR Copyright (C) 2018 <pabr@pabr.org>.
// See the toplevel README for more information.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef LEANSDR_DISCRMATH_H
#define LEANSDR_DISCRMATH_H
#include <cstddef>
namespace leansdr
{
// Polynomials with N binary coefficients.
// This is designed to compile into trivial inline code.
// Bits are packed into words of type T.
// Unused most-significant bits of the last word are 0.
// T must be an unsigned type.
template <typename T, int N>
struct bitvect
{
static const size_t WSIZE = sizeof(T) * 8;
static const size_t NW = (N + WSIZE - 1) / WSIZE;
T v[NW];
bitvect() {}
bitvect(T val)
{
v[0] = val;
for (int i = 1; i < NW; ++i)
v[i] = 0;
}
// Assign from another bitvect, with truncation or padding
template <int M>
bitvect<T, N> &copy(const bitvect<T, M> &a)
{
int nw = (a.NW < NW) ? a.NW : NW;
for (int i = 0; i < nw; ++i)
v[i] = a.v[i];
if (M < N)
for (int i = a.NW; i < NW; ++i)
v[i] = 0;
if (M > N)
truncate_to_N();
return *this;
}
bool operator[](unsigned int i) const
{
return v[i / WSIZE] & ((T)1 << (i & (WSIZE - 1)));
}
// Add (in GF(2))
template <int M>
bitvect<T, N> &operator+=(const bitvect<T, M> &a)
{
int nw = (a.NW < NW) ? a.NW : NW;
for (int i = 0; i < nw; ++i)
v[i] ^= a.v[i];
if (M > N)
truncate_to_N();
return *this;
}
// Multiply by X^n
bitvect<T, N> &operator<<=(unsigned int n)
{
if (n >= WSIZE)
fail("shift exceeds word width");
T mask = ~(((T)-1) << n);
for (int i = NW - 1; i > 0; --i)
v[i] = (v[i] << n) | ((v[i - 1] >> (WSIZE - n)) & mask);
v[0] <<= n;
if (N != NW * WSIZE)
truncate_to_N();
return *this;
}
private:
// Call this whenever bits N .. NW*WSIZE-1 may have been set.
inline void truncate_to_N()
{
v[NW - 1] &= ((T)-1) >> (NW * WSIZE - N);
}
}; // bitvect
// Return m modulo (X^N+p).
// p is typically a generator polynomial of degree N
// with the highest-coefficient monomial ommitted.
// m is packed into nm words of type Tm.
// The MSB of m[0] is the highest-order coefficient of m.
template <typename T, int N, typename Tm>
bitvect<T, N> divmod(const Tm *m, size_t nm, const bitvect<T, N> &p)
{
bitvect<T, N> res = 0;
const Tm bitmask = (Tm)1 << (sizeof(Tm) * 8 - 1);
for (; nm--; ++m)
{
Tm mi = *m;
for (int bit = sizeof(Tm) * 8; bit--; mi <<= 1)
{
// Multiply by X, save outgoing coeff of degree N
bool resN = res[N - 1];
res <<= 1;
// Add m[i]
if (mi & bitmask)
res.v[0] ^= 1;
// Modulo X^N+p
if (resN)
res += p;
}
}
return res;
}
// Return (m*X^N) modulo (X^N+p).
// Same as divmod(), slightly faster than appending N zeroes.
template <typename T, int N, typename Tm>
bitvect<T, N> shiftdivmod(const Tm *m, size_t nm, const bitvect<T, N> &p,
T init = 0)
{
bitvect<T, N> res;
for (int i = 0; i < res.NW; ++i)
res.v[i] = init;
const Tm bitmask = (Tm)1 << (sizeof(Tm) * 8 - 1);
for (; nm--; ++m)
{
Tm mi = *m;
for (int bit = sizeof(Tm) * 8; bit--; mi <<= 1)
{
// Multiply by X, save outgoing coeff of degree N
bool resN = res[N - 1];
res <<= 1;
// Add m[i]*X^N
resN ^= (bool)(mi & bitmask);
// Modulo X^N+p
if (resN)
res += p;
}
}
return res;
}
template <typename T, int N>
bool operator==(const bitvect<T, N> &a, const bitvect<T, N> &b)
{
for (int i = 0; i < a.NW; ++i)
if (a.v[i] != b.v[i])
return false;
return true;
}
// Add (in GF(2))
template <typename T, int N>
bitvect<T, N> operator+(const bitvect<T, N> &a, const bitvect<T, N> &b)
{
bitvect<T, N> res;
for (int i = 0; i < a.NW; ++i)
res.v[i] = a.v[i] ^ b.v[i];
return res;
}
// Polynomial multiplication.
template <typename T, int N, int NB>
bitvect<T, N> operator*(bitvect<T, N> a, const bitvect<T, NB> &b)
{
bitvect<T, N> res = 0;
for (int i = 0; i < NB; ++i, a <<= 1)
if (b[i])
res += a;
// TBD If truncation is needed, do it only once at the end.
return res;
}
// Finite group GF(2^N), for small N.
// GF(2) is the ring ({0,1},+,*).
// GF(2)[X] is the ring of polynomials with coefficients in GF(2).
// P(X) is an irreducible polynomial of GF(2)[X].
// N is the degree of P(x).
// GF(2)[X]/(P) is GF(2)[X] modulo P(X).
// (GF(2)[X]/(P), +) is a group with 2^N elements.
// (GF(2)[X]/(P)*, *) is a group with 2^N-1 elements.
// (GF(2)[X]/(P), +, *) is a field with 2^N elements, noted GF(2^N).
// Te is a C++ integer type for encoding elements of GF(2^N).
// Binary coefficients are packed, with degree 0 at LSB.
// (Te)0 is 0
// (Te)1 is 1
// (Te)2 is X
// (Te)3 is X+1
// (Te)4 is X^2
// TRUNCP is the encoding of the generator, with highest monomial omitted.
// ALPHA is a primitive element of GF(2^N). Usually "2"=[X] is chosen.
template <typename Te, int N, Te ALPHA, Te TRUNCP>
struct gf2n
{
typedef Te element;
static const Te alpha = ALPHA;
gf2n()
{
if (ALPHA != 2)
fail("alpha!=2 not implemented");
// Precompute log and exp tables.
Te alpha_i = 1; // ALPHA^0
for (int i = 0; i < (1 << N); ++i)
{
lut_exp[i] = alpha_i; // ALPHA^i
lut_exp[((1 << N) - 1) + i] = alpha_i; // Wrap to avoid modulo 2^N-1
lut_log[alpha_i] = i;
bool overflow = alpha_i & (1 << (N - 1));
alpha_i <<= 1; // Multiply by alpha=[X] i.e. increase degrees
alpha_i &= ~((~(Te)0) << N); // In case Te is wider than N bits
if (overflow)
alpha_i ^= TRUNCP; // Modulo P iteratively
}
}
inline Te add(Te x, Te y) { return x ^ y; } // Addition modulo 2
inline Te sub(Te x, Te y) { return x ^ y; } // Subtraction modulo 2
inline Te mul(Te x, Te y)
{
if (!x || !y)
return 0;
return lut_exp[lut_log[x] + lut_log[y]];
}
inline Te div(Te x, Te y)
{
if (!x)
return 0;
return lut_exp[lut_log[x] + ((1 << N) - 1) - lut_log[y]];
}
inline Te inv(Te x)
{
return lut_exp[((1 << N) - 1) - lut_log[x]];
}
inline Te exp(Te x) { return lut_exp[x]; }
inline Te log(Te x) { return lut_log[x]; }
private:
Te lut_exp[(1 << N) * 2]; // Extra room for wrapping modulo 2^N-1
Te lut_log[1 << N];
}; // gf2n
} // namespace leansdr
#endif // LEANSDR_DISCRMATH_H

View File

@ -1,9 +1,25 @@
// This file is part of LeanSDR Copyright (C) 2016-2018 <pabr@pabr.org>.
// See the toplevel README for more information.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef LEANSDR_DSP_H
#define LEANSDR_DSP_H
#include <math.h>
#include "leansdr/framework.h"
#include "leansdr/math.h"
#include <math.h>
namespace leansdr
{
@ -18,11 +34,11 @@ template<typename Tin, int Zin, typename Tout, int Zout, int Gn, int Gd>
struct cconverter : runnable
{
cconverter(scheduler *sch, pipebuf<complex<Tin>> &_in,
pipebuf<complex<Tout> > &_out) :
runnable(sch, "cconverter"), in(_in), out(_out)
pipebuf<complex<Tout>> &_out)
: runnable(sch, "cconverter"),
in(_in), out(_out)
{
}
void run()
{
unsigned long count = min(in.readable(), out.writable());
@ -45,10 +61,8 @@ private:
template <typename T>
struct cfft_engine
{
const unsigned int n;
cfft_engine(unsigned int _n) :
n(_n), invsqrtn(1.0 / sqrt(n))
const int n;
cfft_engine(int _n) : n(_n), invsqrtn(1.0 / sqrt(n))
{
// Compute log2(n)
logn = 0;
@ -56,7 +70,7 @@ struct cfft_engine
++logn;
// Bit reversal
bitrev = new int[n];
for (unsigned int i = 0; i < n; ++i)
for (int i = 0; i < n; ++i)
{
bitrev[i] = 0;
for (int b = 0; b < logn; ++b)
@ -65,20 +79,19 @@ struct cfft_engine
// Float constants
omega = new complex<T>[n];
omega_rev = new complex<T>[n];
for (unsigned int i = 0; i < n; ++i)
for (int i = 0; i < n; ++i)
{
float a = 2.0 * M_PI * i / n;
omega_rev[i].re = (omega[i].re = cosf(a));
omega_rev[i].im = -(omega[i].im = sinf(a));
}
}
void inplace(complex<T> *data, bool reverse = false)
{
// Bit-reversal permutation
for (unsigned int i = 0; i < n; ++i)
for (int i = 0; i < n; ++i)
{
unsigned int r = bitrev[i];
int r = bitrev[i];
if (r < i)
{
complex<T> tmp = data[i];
@ -111,7 +124,7 @@ struct cfft_engine
if (reverse)
{
float invn = 1.0 / n;
for (unsigned int i = 0; i < n; ++i)
for (int i = 0; i < n; ++i)
{
data[i].re *= invn;
data[i].im *= invn;
@ -129,14 +142,12 @@ private:
template <typename T>
struct adder : runnable
{
adder(scheduler *sch, pipebuf<T> &_in1, pipebuf<T> &_in2, pipebuf<T> &_out) :
runnable(sch, "adder"),
in1(_in1),
in2(_in2),
out(_out)
adder(scheduler *sch,
pipebuf<T> &_in1, pipebuf<T> &_in2, pipebuf<T> &_out)
: runnable(sch, "adder"),
in1(_in1), in2(_in2), out(_out)
{
}
void run()
{
int n = out.writable();
@ -144,8 +155,7 @@ struct adder: runnable
n = in1.readable();
if (in2.readable() < n)
n = in2.readable();
T *pin1 = in1.rd(), *pin2 = in2.rd(), *pout = out.wr(), *pend = pout
+ n;
T *pin1 = in1.rd(), *pin2 = in2.rd(), *pout = out.wr(), *pend = pout + n;
while (pout < pend)
*pout++ = *pin1++ + *pin2++;
in1.read(n);
@ -162,15 +172,13 @@ template<typename Tscale, typename Tin, typename Tout>
struct scaler : runnable
{
Tscale scale;
scaler(scheduler *sch, Tscale _scale, pipebuf<Tin> &_in, pipebuf<Tout> &_out) :
runnable(sch, "scaler"),
scaler(scheduler *sch, Tscale _scale,
pipebuf<Tin> &_in, pipebuf<Tout> &_out)
: runnable(sch, "scaler"),
scale(_scale),
in(_in),
out(_out)
in(_in), out(_out)
{
}
void run()
{
unsigned long count = min(in.readable(), out.writable());
@ -192,13 +200,10 @@ private:
template <typename T>
struct wgn_c : runnable
{
wgn_c(scheduler *sch, pipebuf<complex<T> > &_out) :
runnable(sch, "awgn"),
stddev(1.0),
out(_out)
wgn_c(scheduler *sch, pipebuf<complex<T>> &_out)
: runnable(sch, "awgn"), stddev(1.0), out(_out)
{
}
void run()
{
int n = out.writable();
@ -220,7 +225,6 @@ struct wgn_c: runnable
}
out.written(n);
}
float stddev;
private:
@ -230,11 +234,8 @@ private:
template <typename T>
struct naive_lowpass : runnable
{
naive_lowpass(scheduler *sch, pipebuf<T> &_in, pipebuf<T> &_out, int _w) :
runnable(sch, "lowpass"),
in(_in),
out(_out),
w(_w)
naive_lowpass(scheduler *sch, pipebuf<T> &_in, pipebuf<T> &_out, int _w)
: runnable(sch, "lowpass"), in(_in), out(_out), w(_w)
{
}
@ -266,16 +267,14 @@ private:
template <typename T, typename Tc>
struct fir_filter : runnable
{
fir_filter(scheduler *sch, int _ncoeffs, Tc *_coeffs, pipebuf<T> &_in, pipebuf<T> &_out, unsigned int _decim = 1) :
runnable(sch, "fir_filter"),
freq_tap(NULL),
tap_multiplier(1),
freq_tol(0.1),
ncoeffs(_ncoeffs),
coeffs(_coeffs),
in(_in),
out(_out),
decim(_decim)
fir_filter(scheduler *sch, int _ncoeffs, Tc *_coeffs,
pipebuf<T> &_in, pipebuf<T> &_out,
unsigned int _decim = 1)
: runnable(sch, "fir_filter"),
ncoeffs(_ncoeffs), coeffs(_coeffs),
in(_in), out(_out),
decim(_decim),
freq_tap(NULL), tap_multiplier(1), freq_tol(0.1)
{
shifted_coeffs = new T[ncoeffs];
set_freq(0);
@ -292,16 +291,15 @@ struct fir_filter: runnable
if (fabs(current_freq - new_freq) > freq_tol)
{
if (sch->verbose)
fprintf(stderr, "Shifting filter %f -> %f\n", current_freq,
new_freq);
fprintf(stderr, "Shifting filter %f -> %f\n",
current_freq, new_freq);
set_freq(new_freq);
}
}
unsigned long count = min((in.readable() - ncoeffs) / decim,
out.writable());
T *pin = in.rd() + ncoeffs, *pend = pin + count * decim, *pout =
out.wr();
T *pin = in.rd() + ncoeffs, *pend = pin + count * decim, *pout = out.wr();
// TBD use coeffs when current_freq=0 (fewer mults if float)
for (; pin < pend; pin += decim, ++pout)
{
@ -316,25 +314,20 @@ struct fir_filter: runnable
out.written(count);
}
public:
float *freq_tap;
float tap_multiplier;
float freq_tol;
private:
unsigned int ncoeffs;
Tc *coeffs;
pipereader<T> in;
pipewriter<T> out;
unsigned int decim;
T *shifted_coeffs;
float current_freq;
void set_freq(float f)
{
for (unsigned int i = 0; i < ncoeffs; ++i)
{
float a = 2 * M_PI * f * (i - (ncoeffs / 2.0f));
float a = 2 * M_PI * f * (i - ncoeffs / 2);
float c = cosf(a), s = sinf(a);
// TBD Support T=complex
shifted_coeffs[i].re = coeffs[i] * c;
@ -342,30 +335,29 @@ private:
}
current_freq = f;
}
};
// fir_filter
public:
float *freq_tap;
float tap_multiplier;
float freq_tol;
}; // fir_filter
// FIR FILTER WITH INTERPOLATION AND DECIMATION
template <typename T, typename Tc>
struct fir_resampler : runnable
{
fir_resampler(scheduler *sch, int _ncoeffs, Tc *_coeffs, pipebuf<T> &_in, pipebuf<T> &_out, int _interp = 1, int _decim = 1) :
runnable(sch, "fir_resampler"),
ncoeffs(_ncoeffs),
coeffs(_coeffs),
interp(_interp),
decim(_decim),
in(_in),
out(_out, interp),
freq_tap(NULL),
tap_multiplier(1),
freq_tol(0.1)
fir_resampler(scheduler *sch, int _ncoeffs, Tc *_coeffs,
pipebuf<T> &_in, pipebuf<T> &_out,
int _interp = 1, int _decim = 1)
: runnable(sch, "fir_resampler"),
ncoeffs(_ncoeffs), coeffs(_coeffs),
interp(_interp), decim(_decim),
in(_in), out(_out, interp),
freq_tap(NULL), tap_multiplier(1), freq_tol(0.1)
{
if (decim != 1) {
fail("fir_resampler::fir_resampler", "decim not implemented"); // TBD
return;
}
if (decim != 1)
fail("fir_resampler: decim not implemented"); // TBD
shifted_coeffs = new T[ncoeffs];
set_freq(0);
}
@ -381,8 +373,8 @@ struct fir_resampler: runnable
if (fabs(current_freq - new_freq) > freq_tol)
{
if (sch->verbose)
fprintf(stderr, "Shifting filter %f -> %f\n", current_freq,
new_freq);
fprintf(stderr, "Shifting filter %f -> %f\n",
current_freq, new_freq);
set_freq(new_freq);
}
}
@ -410,20 +402,21 @@ struct fir_resampler: runnable
out.written(count * interp);
}
public:
float *freq_tap;
float tap_multiplier;
float freq_tol;
private:
unsigned int ncoeffs;
Tc *coeffs;
int interp, decim;
pipereader<T> in;
pipewriter<T> out;
public:
float *freq_tap;
float tap_multiplier;
float freq_tol;
private:
T *shifted_coeffs;
float current_freq;
void set_freq(float f)
{
for (int i = 0; i < ncoeffs; ++i)
@ -436,9 +429,8 @@ private:
}
current_freq = f;
}
};
// fir_resampler
}; // fir_resampler
}// namespace
} // namespace leansdr
#endif // LEANSDR_DSP_H

View File

@ -0,0 +1,46 @@
#include "dvb.h"
namespace leansdr
{
deconvol_sync_simple *make_deconvol_sync_simple(scheduler *sch,
pipebuf<eucl_ss> &_in,
pipebuf<u8> &_out,
enum code_rate rate)
{
// EN 300 421, section 4.4.3 Inner coding
uint32_t pX, pY;
switch (rate)
{
case FEC12:
pX = 0x1; // 1
pY = 0x1; // 1
break;
case FEC23:
case FEC46:
pX = 0xa; // 1010 (Handle as FEC4/6, no half-symbols)
pY = 0xf; // 1111
break;
case FEC34:
pX = 0x5; // 101
pY = 0x6; // 110
break;
case FEC56:
pX = 0x15; // 10101
pY = 0x1a; // 11010
break;
case FEC78:
pX = 0x45; // 1000101
pY = 0x7a; // 1111010
break;
default:
//fail("Code rate not implemented");
// For testing DVB-S2 constellations.
fprintf(stderr, "Code rate not implemented; proceeding anyway\n");
pX = pY = 1;
}
return new deconvol_sync_simple(sch, _in, _out, DVBS_G1, DVBS_G2, pX, pY);
}
} // leansdr

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,20 @@
#include <stdio.h>
#include "filtergen.h"
namespace leansdr
{
namespace filtergen
{
void dump_filter(const char *name, int ncoeffs, float *coeffs)
{
fprintf(stderr, "%s = [", name);
for (int i = 0; i < ncoeffs; ++i)
fprintf(stderr, "%s %f", (i ? "," : ""), coeffs[i]);
fprintf(stderr, " ];\n");
}
} // filtergen
} // leansdr

View File

@ -1,35 +1,75 @@
// This file is part of LeanSDR Copyright (C) 2016-2018 <pabr@pabr.org>.
// See the toplevel README for more information.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef LEANSDR_FILTERGEN_H
#define LEANSDR_FILTERGEN_H
#include <math.h>
namespace leansdr {
namespace filtergen {
#include "framework.h"
namespace leansdr
{
namespace filtergen
{
template <typename T>
void normalize_power(int n, T *coeffs, float gain=1) {
void normalize_power(int n, T *coeffs, float gain = 1)
{
float s2 = 0;
for ( int i=0; i<n; ++i ) s2 = s2 + coeffs[i]*coeffs[i]; // TBD complex
if ( s2 ) gain /= gen_sqrt(s2);
for ( int i=0; i<n; ++i ) coeffs[i] = coeffs[i] * gain;
for (int i = 0; i < n; ++i)
s2 = s2 + coeffs[i] * coeffs[i]; // TBD complex
if (s2)
gain /= gen_sqrt(s2);
for (int i = 0; i < n; ++i)
coeffs[i] = coeffs[i] * gain;
}
template <typename T>
void normalize_dcgain(int n, T *coeffs, float gain=1) {
void normalize_dcgain(int n, T *coeffs, float gain = 1)
{
float s = 0;
for ( int i=0; i<n; ++i ) s = s + coeffs[i];
if ( s ) gain /= s;
for ( int i=0; i<n; ++i ) coeffs[i] = coeffs[i] * gain;
for (int i = 0; i < n; ++i)
s = s + coeffs[i];
if (s)
gain /= s;
for (int i = 0; i < n; ++i)
coeffs[i] = coeffs[i] * gain;
}
template <typename T>
void cancel_dcgain(int n, T *coeffs)
{
float s = 0;
for (int i = 0; i < n; ++i)
s = s + coeffs[i];
for (int i = 0; i < n; ++i)
coeffs[i] -= s / n;
}
// Generate coefficients for a sinc filter.
// https://en.wikipedia.org/wiki/Sinc_filter
template <typename T>
int lowpass(int order, float Fcut, T **coeffs, float gain=1) {
int lowpass(int order, float Fcut, T **coeffs, float gain = 1)
{
int ncoeffs = order + 1;
*coeffs = new T[ncoeffs];
for ( int i=0; i<ncoeffs; ++i ) {
for (int i = 0; i < ncoeffs; ++i)
{
float t = i - (ncoeffs - 1) * 0.5;
float sinc = 2 * Fcut * (t ? sin(2 * M_PI * Fcut * t) / (2 * M_PI * Fcut * t) : 1);
#if 0 // Hamming
@ -44,29 +84,29 @@ namespace leansdr {
return ncoeffs;
}
// Generate coefficients for a RRC filter.
// https://en.wikipedia.org/wiki/Root-raised-cosine_filter
template <typename T>
int root_raised_cosine(int order, float Fs, float rolloff, T **coeffs) {
int root_raised_cosine(int order, float Fs, float rolloff, T **coeffs)
{
float B = rolloff, pi = M_PI;
int ncoeffs = (order + 1) | 1;
*coeffs = new T[ncoeffs];
for ( int i=0; i<ncoeffs; ++i ) {
for (int i = 0; i < ncoeffs; ++i)
{
int t = i - ncoeffs / 2;
float c;
if (t == 0)
c = sqrt(Fs) * (1 - B + 4 * B / pi);
else {
else
{
float tT = t * Fs;
float den = pi * tT * (1 - (4 * B * tT) * (4 * B * tT));
if (!den)
c = B*sqrt(Fs/2) * ( (1+2/pi)*sin(pi/(4*B)) +
(1-2/pi)*cos(pi/(4*B)) );
c = B * sqrt(Fs / 2) * ((1 + 2 / pi) * sin(pi / (4 * B)) + (1 - 2 / pi) * cos(pi / (4 * B)));
else
c = sqrt(Fs) * ( sin(pi*tT*(1-B)) +
4*B*tT*cos(pi*tT*(1+B)) ) / den;
c = sqrt(Fs) * (sin(pi * tT * (1 - B)) + 4 * B * tT * cos(pi * tT * (1 + B))) / den;
}
(*coeffs)[i] = c;
}
@ -74,17 +114,10 @@ namespace leansdr {
return ncoeffs;
}
// Dump filter coefficients for matlab/octave
void dump_filter(const char *name, int ncoeffs, float *coeffs);
inline void dump_filter(const char *name, int ncoeffs, float *coeffs) {
fprintf(stderr, "%s = [", name);
for ( int i=0; i<ncoeffs; ++i )
fprintf(stderr, "%s %f", (i?",":""), coeffs[i]);
fprintf(stderr, " ];\n");
}
} // namespace
} // namespace
} // namespace filtergen
} // namespace leansdr
#endif // LEANSDR_FILTERGEN_H

View File

@ -0,0 +1,16 @@
#include "framework.h"
namespace leansdr
{
void fatal(const char *s)
{
perror(s);
}
void fail(const char *s)
{
fprintf(stderr, "** %s\n", s);
}
} // leansdr

View File

@ -1,26 +1,37 @@
// This file is part of LeanSDR Copyright (C) 2016-2018 <pabr@pabr.org>.
// See the toplevel README for more information.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef LEANSDR_FRAMEWORK_H
#define LEANSDR_FRAMEWORK_H
#include <cstddef>
#include <algorithm>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#ifndef VERSION
#define VERSION "undefined"
#endif
namespace leansdr
{
inline void fatal(const char *s)
{
perror(s);
}
inline void fail(const char *f, const char *s)
{
fprintf(stderr, "leansdr::%s: %s\n", f, s);
}
void fatal(const char *s);
void fail(const char *s);
//////////////////////////////////////////////////////////////////////
// DSP framework
@ -53,22 +64,22 @@ struct pipebuf_common
(void)total_bufs;
}
pipebuf_common(const char *_name) :
name(_name)
const char *name;
pipebuf_common(const char *_name) : name(_name)
{
}
virtual ~pipebuf_common()
{
}
const char *name;
};
struct runnable_common
{
runnable_common(const char *_name) :
name(_name)
const char *name;
runnable_common(const char *_name) : name(_name)
{
}
@ -83,13 +94,12 @@ struct runnable_common
virtual void shutdown()
{
}
#ifdef DEBUG
~runnable_common()
{ fprintf(stderr, "Deallocating %s !\n", name);}
{
fprintf(stderr, "Deallocating %s !\n", name);
}
#endif
const char *name;
};
struct window_placement
@ -105,40 +115,36 @@ struct scheduler
runnable_common *runnables[MAX_RUNNABLES];
int nrunnables;
window_placement *windows;
bool verbose, debug;
bool verbose, debug, debug2;
scheduler() :
npipes(0), nrunnables(0), windows(nullptr), verbose(false), debug(false)
scheduler() : npipes(0),
nrunnables(0),
windows(NULL),
verbose(false),
debug(false),
debug2(false)
{
std::fill(pipes, pipes + MAX_PIPES, nullptr);
std::fill(runnables, runnables + MAX_RUNNABLES, nullptr);
}
void add_pipe(pipebuf_common *p)
{
if (npipes == MAX_PIPES)
{
fail("scheduler::add_pipe", "MAX_PIPES");
return;
}
fail("MAX_PIPES");
pipes[npipes++] = p;
}
void add_runnable(runnable_common *r)
{
if (nrunnables == MAX_RUNNABLES)
{
fail("scheduler::add_runnable", "MAX_RUNNABLES");
}
fail("MAX_RUNNABLES");
runnables[nrunnables++] = r;
}
void step()
{
for (int i = 0; i < nrunnables; ++i) {
for (int i = 0; i < nrunnables; ++i)
runnables[i]->run();
}
}
void run()
{
@ -148,26 +154,23 @@ struct scheduler
{
step();
unsigned long long h = hash();
if (h == prev_hash) {
if (h == prev_hash)
break;
}
prev_hash = h;
}
}
void shutdown()
{
for (int i = 0; i < nrunnables; ++i) {
for (int i = 0; i < nrunnables; ++i)
runnables[i]->shutdown();
}
}
unsigned long long hash()
{
unsigned long long h = 0;
for (int i = 0; i < npipes; ++i) {
for (int i = 0; i < npipes; ++i)
h += (1 + i) * pipes[i]->hash();
}
return h;
}
@ -175,20 +178,20 @@ struct scheduler
{
fprintf(stderr, "\n");
std::size_t total_bufs = 0;
for (int i = 0; i < npipes; ++i) {
for (int i = 0; i < npipes; ++i)
pipes[i]->dump(&total_bufs);
}
fprintf(stderr, "leansdr::scheduler::dump Total buffer memory: %ld KiB\n", (unsigned long) total_bufs / 1024);
fprintf(stderr, "Total buffer memory: %ld KiB\n",
(unsigned long)total_bufs / 1024);
}
};
struct runnable : runnable_common
{
runnable(scheduler *_sch, const char *name) :
runnable_common(name), sch(_sch)
runnable(scheduler *_sch, const char *name) : runnable_common(name), sch(_sch)
{
sch->add_runnable(this);
}
protected:
scheduler *sch;
};
@ -207,9 +210,13 @@ struct pipebuf: pipebuf_common
return sizeof(T);
}
pipebuf(scheduler *sch, const char *name, unsigned long size) :
pipebuf_common(name), buf(new T[size]), nrd(0), wr(buf), end(
buf + size), min_write(1), total_written(0), total_read(0)
pipebuf(scheduler *sch, const char *name, unsigned long size) : pipebuf_common(name),
buf(new T[size]),
nrd(0), wr(buf),
end(buf + size),
min_write(1),
total_written(0),
total_read(0)
{
sch->add_pipe(this);
}
@ -217,10 +224,7 @@ struct pipebuf: pipebuf_common
int add_reader()
{
if (nrd == MAX_READERS)
{
fail("pipebuf::add_reader", "too many readers");
return nrd;
}
fail("too many readers");
rds[nrd] = wr;
return nrd++;
}
@ -229,17 +233,13 @@ struct pipebuf: pipebuf_common
{
T *rd = wr;
for (int i = 0; i < nrd; ++i)
{
if (rds[i] < rd) {
if (rds[i] < rd)
rd = rds[i];
}
}
memmove(buf, rd, (wr - rd) * sizeof(T));
wr -= rd - buf;
for (int i = 0; i < nrd; ++i) {
for (int i = 0; i < nrd; ++i)
rds[i] -= rd - buf;
}
}
long long hash()
{
@ -248,39 +248,34 @@ struct pipebuf: pipebuf_common
void dump(std::size_t *total_bufs)
{
if (total_written < 10000) {
fprintf(stderr, "leansdr::pipebuf::dump: .%-16s : %4ld/%4ld", name, total_read, total_written);
} else if (total_written < 1000000) {
fprintf(stderr, "leansdr::pipebuf::dump: .%-16s : %3ldk/%3ldk", name, total_read / 1000, total_written / 1000);
} else {
fprintf(stderr, "leansdr::pipebuf::dump: .%-16s : %3ldM/%3ldM", name, total_read / 1000000, total_written / 1000000);
}
if (total_written < 10000)
fprintf(stderr, ".%-16s : %4ld/%4ld", name, total_read,
total_written);
else if (total_written < 1000000)
fprintf(stderr, ".%-16s : %3ldk/%3ldk", name, total_read / 1000,
total_written / 1000);
else
fprintf(stderr, ".%-16s : %3ldM/%3ldM", name, total_read / 1000000,
total_written / 1000000);
*total_bufs += (end - buf) * sizeof(T);
unsigned long nw = end - wr;
fprintf(stderr, "leansdr::pipebuf: %6ld writable %c,", nw, (nw < min_write) ? '!' : ' ');
fprintf(stderr, " %6ld writable %c,", nw, (nw < min_write) ? '!' : ' ');
T *rd = wr;
for (int j = 0; j < nrd; ++j)
{
if (rds[j] < rd) {
if (rds[j] < rd)
rd = rds[j];
}
}
fprintf(stderr, "leansdr::pipebuf::dump: %6d unread (", (int) (wr - rd));
for (int j = 0; j < nrd; ++j) {
fprintf(stderr, "leansdr::pipebuf: %d", (int) (wr - rds[j]));
}
fprintf(stderr, "leansdr::pipebuf::dump: )\n");
fprintf(stderr, " %6d unread (", (int)(wr - rd));
for (int j = 0; j < nrd; ++j)
fprintf(stderr, " %d", (int)(wr - rds[j]));
fprintf(stderr, " )\n");
}
unsigned long min_write;
unsigned long total_written, total_read;
#ifdef DEBUG
~pipebuf()
{ fprintf(stderr, "Deallocating %s !\n", name);}
{
fprintf(stderr, "Deallocating %s !\n", name);
}
#endif
};
@ -289,30 +284,17 @@ struct pipewriter
{
pipebuf<T> &buf;
pipewriter(pipebuf<T> &_buf, unsigned long min_write = 1) :
buf(_buf)
pipewriter(pipebuf<T> &_buf, unsigned long min_write = 1) : buf(_buf)
{
if (min_write > buf.min_write) {
if (min_write > buf.min_write)
buf.min_write = min_write;
}
}
/** Return number of items writable at this->wr, 0 if full. */
unsigned long writable()
// Return number of items writable at this->wr, 0 if full.
long writable()
{
if (buf.end < buf.wr)
{
fprintf(stderr, "leansdr::pipewriter::writable: overflow in %s buffer\n", buf.name);
return 0;
}
unsigned long delta = buf.end - buf.wr;
if (delta < buf.min_write) {
if (buf.end < buf.min_write + buf.wr)
buf.pack();
}
return delta;
return buf.end - buf.wr;
}
T *wr()
@ -324,9 +306,9 @@ struct pipewriter
{
if (buf.wr + n > buf.end)
{
fprintf(stderr, "leansdr::pipewriter::written: overflow in %s buffer\n", buf.name);
return;
fprintf(stderr, "Bug: overflow to %s\n", buf.name);
}
buf.wr += n;
buf.total_written += n;
}
@ -341,13 +323,13 @@ struct pipewriter
// Convenience functions for working with optional pipes
template <typename T>
pipewriter<T> *opt_writer(pipebuf<T> *buf)
pipewriter<T> *opt_writer(pipebuf<T> *buf, unsigned long min_write = 1)
{
return buf ? new pipewriter<T>(*buf) : NULL;
return buf ? new pipewriter<T>(*buf, min_write) : NULL;
}
template <typename T>
bool opt_writable(pipewriter<T> *p, unsigned int n = 1)
bool opt_writable(pipewriter<T> *p, int n = 1)
{
return (p == NULL) || p->writable() >= n;
}
@ -355,10 +337,9 @@ bool opt_writable(pipewriter<T> *p, unsigned int n = 1)
template <typename T>
void opt_write(pipewriter<T> *p, T val)
{
if (p) {
if (p)
p->write(val);
}
}
template <typename T>
struct pipereader
@ -366,12 +347,11 @@ struct pipereader
pipebuf<T> &buf;
int id;
pipereader(pipebuf<T> &_buf) :
buf(_buf), id(_buf.add_reader())
pipereader(pipebuf<T> &_buf) : buf(_buf), id(_buf.add_reader())
{
}
unsigned long readable()
long readable()
{
return buf.wr - buf.rds[id];
}
@ -385,9 +365,9 @@ struct pipereader
{
if (buf.rds[id] + n > buf.wr)
{
fprintf(stderr, "leansdr::pipereader::read: underflow in %s buffer\n", buf.name);
return;
fprintf(stderr, "Bug: underflow from %s\n", buf.name);
}
buf.rds[id] += n;
buf.total_read += n;
}
@ -395,7 +375,8 @@ struct pipereader
// Math functions for templates
template<typename T> T gen_sqrt(T x);
template <typename T>
T gen_sqrt(T x);
inline float gen_sqrt(float x)
{
return sqrtf(x);
@ -411,7 +392,8 @@ inline long double gen_sqrt(long double x)
return sqrtl(x);
}
template<typename T> T gen_abs(T x);
template <typename T>
T gen_abs(T x);
inline float gen_abs(float x)
{
return fabsf(x);
@ -427,7 +409,8 @@ inline long int gen_abs(long int x)
return labs(x);
}
template<typename T> T gen_hypot(T x, T y);
template <typename T>
T gen_hypot(T x, T y);
inline float gen_hypot(float x, float y)
{
return hypotf(x, y);
@ -438,7 +421,8 @@ inline long double gen_hypot(long double x, long double y)
return hypotl(x, y);
}
template<typename T> T gen_atan2(T y, T x);
template <typename T>
T gen_atan2(T y, T x);
inline float gen_atan2(float y, float x)
{
return atan2f(y, x);
@ -470,6 +454,6 @@ typedef signed char s8;
typedef signed short s16;
typedef signed long s32;
} // namespace
} // namespace leansdr
#endif // LEANSDR_FRAMEWORK_H

View File

@ -1,14 +1,26 @@
// This file is part of LeanSDR Copyright (C) 2016-2018 <pabr@pabr.org>.
// See the toplevel README for more information.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef LEANSDR_GENERIC_H
#define LEANSDR_GENERIC_H
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#ifdef _MSC_VER
#include <stdlib.h>
#include <io.h>
#else
#include <unistd.h>
#endif
#include "leansdr/math.h"
@ -25,8 +37,11 @@ namespace leansdr
template <typename T>
struct file_reader : runnable
{
file_reader(scheduler *sch, int _fdin, pipebuf<T> &_out) :
runnable(sch, _out.name), loop(false), fdin(_fdin), out(_out)
file_reader(scheduler *sch, int _fdin, pipebuf<T> &_out)
: runnable(sch, _out.name),
loop(false),
filler(NULL),
fdin(_fdin), out(_out)
{
}
void run()
@ -35,32 +50,29 @@ struct file_reader: runnable
if (!size)
return;
#ifdef _MSC_VER
again: ssize_t nr = _read(fdin, out.wr(), size);
#else
again: ssize_t nr = read(fdin, out.wr(), size);
#endif
if (nr < 0)
again:
ssize_t nr = read(fdin, out.wr(), size);
if (nr < 0 && errno == EWOULDBLOCK)
{
fatal("leansdr::file_reader::run: read");
if (filler)
{
if (sch->debug)
fprintf(stderr, "U");
out.write(*filler);
}
return;
}
if (nr < 0)
fatal("read(file_reader)");
if (!nr)
{
if (!loop)
return;
if (sch->debug)
fprintf(stderr, "leansdr::file_reader::run: %s looping\n", name);
#ifdef _MSC_VER
off_t res = _lseek(fdin, 0, SEEK_SET);
#else
fprintf(stderr, "%s looping\n", name);
off_t res = lseek(fdin, 0, SEEK_SET);
#endif
if (res == (off_t)-1)
{
fatal("leansdr::file_reader::run: lseek");
return;
}
fatal("lseek");
goto again;
}
@ -71,16 +83,9 @@ struct file_reader: runnable
{
if (sch->debug)
fprintf(stderr, "+");
#ifdef _MSC_VER
ssize_t nr2 = _read(fdin, (char*) out.wr() + nr, remain);
#else
ssize_t nr2 = read(fdin, (char *)out.wr() + nr, remain);
#endif
if (nr2 <= 0)
{
fatal("leansdr::file_reader::run: partial read");
return;
}
fatal("partial read");
nr += nr2;
remain -= nr2;
}
@ -88,7 +93,16 @@ struct file_reader: runnable
out.written(nr / sizeof(T));
}
bool loop;
void set_realtime(T &_filler)
{
int flags = fcntl(fdin, F_GETFL);
if (fcntl(fdin, F_SETFL, flags | O_NONBLOCK))
fatal("fcntl");
filler = new T(_filler);
}
private:
T *filler;
int fdin;
pipewriter<T> out;
};
@ -98,8 +112,8 @@ private:
template <typename T>
struct file_writer : runnable
{
file_writer(scheduler *sch, pipebuf<T> &_in, int _fdout) :
runnable(sch, _in.name), in(_in), fdout(_fdout)
file_writer(scheduler *sch, pipebuf<T> &_in, int _fdout) : runnable(sch, _in.name),
in(_in), fdout(_fdout)
{
}
void run()
@ -107,28 +121,16 @@ struct file_writer: runnable
int size = in.readable() * sizeof(T);
if (!size)
return;
#ifdef _MSC_VER
int nw = _write(fdout, in.rd(), size);
#else
int nw = write(fdout, in.rd(), size);
#endif
if (!nw)
{
fatal("leansdr::file_writer::run: pipe");
return;
}
fatal("pipe");
if (nw < 0)
{
fatal("leansdr::file_writer::run: write");
return;
}
fatal("write");
if (nw % sizeof(T))
{
fatal("leansdr::file_writer::run:partial write");
return;
}
fatal("partial write");
in.read(nw / sizeof(T));
}
private:
pipereader<T> in;
int fdout;
@ -140,8 +142,11 @@ private:
template <typename T>
struct file_printer : runnable
{
file_printer(scheduler *sch, const char *_format, pipebuf<T> &_in, int _fdout, int _decimation = 1) :
runnable(sch, _in.name), scale(1), decimation(_decimation), in(_in), format(_format), fdout(_fdout), phase(0)
file_printer(scheduler *sch, const char *_format,
pipebuf<T> &_in, int _fdout,
int _decimation = 1) : runnable(sch, _in.name),
scale(1), decimation(_decimation),
in(_in), format(_format), fdout(_fdout), phase(0)
{
}
void run()
@ -156,26 +161,17 @@ struct file_printer: runnable
char buf[256];
int len = snprintf(buf, sizeof(buf), format, (*pin) * scale);
if (len < 0)
{
fatal("leansdr::file_printer::run: obsolete glibc");
return;
}
#ifdef _MSC_VER
int nw = _write(fdout, buf, len);
#else
fatal("obsolete glibc");
int nw = write(fdout, buf, len);
#endif
if (nw != len)
{
fatal("leansdr::file_printer::run: partial write");
return;
}
fatal("partial write");
}
}
in.read(n);
}
T scale;
int decimation;
private:
pipereader<T> in;
const char *format;
@ -190,8 +186,15 @@ private:
template <typename T>
struct file_carrayprinter : runnable
{
file_carrayprinter(scheduler *sch, const char *_head, const char *_format, const char *_sep, const char *_tail, pipebuf<complex<T> > &_in, int _fdout) :
runnable(sch, _in.name), scale(1), fixed_size(0), in(_in), head(_head), format(_format), sep(_sep), tail(_tail), fout(fdopen(_fdout, "w"))
file_carrayprinter(scheduler *sch,
const char *_head,
const char *_format,
const char *_sep,
const char *_tail,
pipebuf<complex<T>> &_in, int _fdout) : runnable(sch, _in.name),
scale(1), fixed_size(0), in(_in),
head(_head), format(_format), sep(_sep), tail(_tail),
fout(fdopen(_fdout, "w"))
{
}
void run()
@ -228,22 +231,26 @@ private:
template <typename T, int N>
struct file_vectorprinter : runnable
{
file_vectorprinter(scheduler *sch, const char *_head, const char *_format, const char *_sep, const char *_tail, pipebuf<T[N]> &_in, int _fdout) :
runnable(sch, _in.name), scale(1), in(_in), head(_head), format(_format), sep(_sep), tail(_tail)
file_vectorprinter(scheduler *sch,
const char *_head,
const char *_format,
const char *_sep,
const char *_tail,
pipebuf<T[N]> &_in, int _fdout, int _n = N) : runnable(sch, _in.name), scale(1), in(_in),
head(_head), format(_format), sep(_sep), tail(_tail), n(_n)
{
fout = fdopen(_fdout, "w");
if (!fout)
{
fatal("leansdr::file_vectorprinter::file_vectorprinter: fdopen");
}
fatal("fdopen");
}
void run()
{
while (in.readable() >= 1)
{
fprintf(fout, head, N);
T (*pin)[N] = in.rd();
for (int i = 0; i < N; ++i)
fprintf(fout, head, n);
T(*pin)
[N] = in.rd();
for (int i = 0; i < n; ++i)
{
if (i)
fprintf(fout, "%s", sep);
@ -255,10 +262,12 @@ struct file_vectorprinter: runnable
fflush(fout);
}
T scale;
private:
pipereader<T[N]> in;
const char *head, *format, *sep, *tail;
FILE *fout;
int n;
};
// [itemcounter] writes the number of input items to the output [pipebuf].
@ -267,8 +276,9 @@ private:
template <typename Tin, typename Tout>
struct itemcounter : runnable
{
itemcounter(scheduler *sch, pipebuf<Tin> &_in, pipebuf<Tout> &_out) :
runnable(sch, "itemcounter"), in(_in), out(_out)
itemcounter(scheduler *sch, pipebuf<Tin> &_in, pipebuf<Tout> &_out)
: runnable(sch, "itemcounter"),
in(_in), out(_out)
{
}
void run()
@ -281,6 +291,7 @@ struct itemcounter: runnable
out.write(count);
in.read(count);
}
private:
pipereader<Tin> in;
pipewriter<Tout> out;
@ -293,8 +304,10 @@ struct decimator: runnable
{
unsigned int d;
decimator(scheduler *sch, int _d, pipebuf<T> &_in, pipebuf<T> &_out) :
runnable(sch, "decimator"), d(_d), in(_in), out(_out)
decimator(scheduler *sch, int _d, pipebuf<T> &_in, pipebuf<T> &_out)
: runnable(sch, "decimator"),
d(_d),
in(_in), out(_out)
{
}
void run()
@ -306,6 +319,7 @@ struct decimator: runnable
in.read(count * d);
out.written(count);
}
private:
pipereader<T> in;
pipewriter<T> out;
@ -319,8 +333,13 @@ struct rate_estimator: runnable
{
int sample_size;
rate_estimator(scheduler *sch, pipebuf<int> &_num, pipebuf<int> &_den, pipebuf<float> &_rate) :
runnable(sch, "rate_estimator"), sample_size(10000), num(_num), den(_den), rate(_rate), acc_num(0), acc_den(0)
rate_estimator(scheduler *sch,
pipebuf<int> &_num, pipebuf<int> &_den,
pipebuf<float> &_rate)
: runnable(sch, "rate_estimator"),
sample_size(10000),
num(_num), den(_den), rate(_rate),
acc_num(0), acc_den(0)
{
}
@ -355,15 +374,13 @@ private:
template <typename Tin, typename Tout>
struct serializer : runnable
{
serializer(scheduler *sch, pipebuf<Tin> &_in, pipebuf<Tout> &_out) :
nin(max((size_t) 1, sizeof(Tin) / sizeof(Tout))), nout(max((size_t) 1, sizeof(Tout) / sizeof(Tin))), in(_in), out(_out, nout)
serializer(scheduler *sch, pipebuf<Tin> &_in, pipebuf<Tout> &_out)
: nin(max((size_t)1, sizeof(Tin) / sizeof(Tout))),
nout(max((size_t)1, sizeof(Tout) / sizeof(Tin))),
in(_in), out(_out, nout)
{
(void) sch;
if (nin * sizeof(Tin) != nout * sizeof(Tout))
{
fail("serializer::serializer", "incompatible sizes");
return;
}
fail("serializer: incompatible sizes");
}
void run()
{
@ -374,20 +391,21 @@ struct serializer: runnable
out.written(nout);
}
}
private:
int nin, nout;
pipereader<Tin> in;
pipewriter<Tout> out;
};
// serializer
}; // serializer
// [buffer_reader] reads from a user-supplied buffer.
template <typename T>
struct buffer_reader : runnable
{
buffer_reader(scheduler *sch, T *_data, int _count, pipebuf<T> &_out) :
runnable(sch, "buffer_reader"), data(_data), count(_count), out(_out), pos(0)
buffer_reader(scheduler *sch, T *_data, int _count, pipebuf<T> &_out)
: runnable(sch, "buffer_reader"),
data(_data), count(_count), out(_out), pos(0)
{
}
void run()
@ -397,21 +415,22 @@ struct buffer_reader: runnable
pos += n;
out.written(n);
}
private:
T *data;
int count;
pipewriter<T> out;
int pos;
};
// buffer_reader
}; // buffer_reader
// [buffer_writer] writes to a user-supplied buffer.
template <typename T>
struct buffer_writer : runnable
{
buffer_writer(scheduler *sch, pipebuf<T> &_in, T *_data, int _count) :
runnable(sch, "buffer_reader"), in(_in), data(_data), count(_count), pos(0)
buffer_writer(scheduler *sch, pipebuf<T> &_in, T *_data, int _count)
: runnable(sch, "buffer_reader"),
in(_in), data(_data), count(_count), pos(0)
{
}
void run()
@ -421,14 +440,14 @@ struct buffer_writer: runnable
in.read(n);
pos += n;
}
private:
pipereader<T> in;
T *data;
int count;
int pos;
};
// buffer_writer
}; // buffer_writer
}// namespace
} // namespace leansdr
#endif // LEANSDR_GENERIC_H

View File

@ -1,3 +1,19 @@
// This file is part of LeanSDR Copyright (C) 2016-2018 <pabr@pabr.org>.
// See the toplevel README for more information.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef LEANSDR_GUI_H
#define LEANSDR_GUI_H
@ -5,7 +21,8 @@
#include "framework.h"
namespace leansdr {
namespace leansdr
{
//////////////////////////////////////////////////////////////////////
// GUI blocks
@ -19,35 +36,42 @@ namespace leansdr {
static const int DEFAULT_GUI_DECIMATION = 64;
struct gfx {
struct gfx
{
Display *display;
int screen;
int w, h;
Window window;
GC gc;
Pixmap dbuf;
gfx(scheduler *sch, const char *name) {
gfx(scheduler *sch, const char *name)
{
window_placement *wp;
for (wp = sch->windows; wp && wp->name; ++wp)
if ( ! strcmp(wp->name, name) ) break;
if (!strcmp(wp->name, name))
break;
if (wp && wp->name)
init(wp->name, wp->x, wp->y, wp->w, wp->h);
else {
else
{
fprintf(stderr, "No placement hints for window '%s'\n", name);
init(name, -1, -1, 320, 240);
}
}
gfx(const char *name, int _x, int _y, int _w, int _h) {
gfx(const char *name, int _x, int _y, int _w, int _h)
{
init(name, _x, _y, _w, _h);
}
void init(const char *name, int _x, int _y, int _w, int _h) {
void init(const char *name, int _x, int _y, int _w, int _h)
{
buttons = 0;
clicks = 0;
mmoved = false;
w = _w;
h = _h;
display = XOpenDisplay(getenv("DISPLAY"));
if ( ! display ) fatal("display");
if (!display)
fatal("display");
screen = DefaultScreen(display);
XSetWindowAttributes xswa;
xswa.event_mask = (ExposureMask |
@ -62,30 +86,39 @@ namespace leansdr {
100, 100, w, h, 10, CopyFromParent, InputOutput,
CopyFromParent, CWEventMask | CWBackPixel,
&xswa);
if ( !window ) fatal("window");
if (!window)
fatal("window");
XStoreName(display, window, name);
XMapWindow(display, window);
if (_x >= 0 && _y >= 0)
XMoveWindow(display, window, _x, _y);
dbuf = XCreatePixmap(display, window, w, h, DefaultDepth(display, screen));
gc = XCreateGC(display, dbuf, 0, NULL);
if ( ! gc ) fatal("gc");
if (!gc)
fatal("gc");
}
void clear() {
void clear()
{
setfg(0, 0, 0);
XFillRectangle(display, dbuf, gc, 0, 0, w, h);
}
void show() {
void show()
{
XCopyArea(display, dbuf, window, gc, 0, 0, w, h, 0, 0);
}
void sync() {
void sync()
{
XSync(display, False);
}
void events() {
void events()
{
XEvent ev;
while ( XCheckWindowEvent(display, window, -1, &ev) ) {
switch ( ev.type ) {
case ButtonPress: {
while (XCheckWindowEvent(display, window, -1, &ev))
{
switch (ev.type)
{
case ButtonPress:
{
int b = ev.xbutton.button;
buttons |= 1 << b;
clicks |= 1 << b;
@ -93,7 +126,8 @@ namespace leansdr {
my = ev.xbutton.y;
break;
}
case ButtonRelease: {
case ButtonRelease:
{
int b = ev.xbutton.button;
buttons &= ~(1 << b);
mx = ev.xbutton.x;
@ -108,24 +142,31 @@ namespace leansdr {
}
}
}
void setfg(unsigned char r, unsigned char g, unsigned char b) {
void setfg(unsigned char r, unsigned char g, unsigned char b)
{
XColor c;
c.red = r<<8; c.green = g<<8; c.blue = b<<8;
c.red = r << 8;
c.green = g << 8;
c.blue = b << 8;
c.flags = DoRed | DoGreen | DoBlue;
if (!XAllocColor(display, DefaultColormap(display, screen), &c))
fatal("color");
XSetForeground(display, gc, c.pixel);
}
void point(int x, int y) {
void point(int x, int y)
{
XDrawPoint(display, dbuf, gc, x, y);
}
void line(int x0, int y0, int x1, int y1) {
void line(int x0, int y0, int x1, int y1)
{
XDrawLine(display, dbuf, gc, x0, y0, x1, y1);
}
void text(int x, int y, const char *s) {
void text(int x, int y, const char *s)
{
XDrawString(display, dbuf, gc, x, y, s, strlen(s));
}
void transient_text(int x, int y, const char *s) {
void transient_text(int x, int y, const char *s)
{
XDrawString(display, window, gc, x, y, s, strlen(s));
}
int buttons; // Mask of button states (2|4|8)
@ -135,36 +176,44 @@ namespace leansdr {
};
template <typename T>
struct cscope : runnable {
struct cscope : runnable
{
T xymin, xymax;
unsigned long decimation;
unsigned long pixels_per_frame;
cstln_lut<256> **cstln; // Optional ptr to optional constellation
cstln_base **cstln; // Optional ptr to optional constellation
cscope(scheduler *sch, pipebuf<complex<T>> &_in, T _xymin, T _xymax,
const char *_name = NULL)
: runnable(sch, _name ? _name : _in.name),
xymin(_xymin), xymax(_xymax),
decimation(DEFAULT_GUI_DECIMATION), pixels_per_frame(1024),
cstln(NULL),
in(_in), phase(0), g(sch, name) {
in(_in), phase(0), g(sch, name)
{
}
void run() {
while ( in.readable() >= pixels_per_frame ) {
if ( ! phase ) {
void run()
{
while (in.readable() >= pixels_per_frame)
{
if (!phase)
{
draw_begin();
g.setfg(0, 255, 0);
complex<T> *p = in.rd(), *pend = p + pixels_per_frame;
for (; p < pend; ++p)
g.point(g.w * (p->re - xymin) / (xymax - xymin),
g.h - g.h * (p->im - xymin) / (xymax - xymin));
if ( cstln && (*cstln) ) {
if (cstln && (*cstln))
{
// Plot constellation points
g.setfg(255, 255, 255);
for ( int i=0; i<(*cstln)->nsymbols; ++i ) {
for (int i = 0; i < (*cstln)->nsymbols; ++i)
{
complex<signed char> *p = &(*cstln)->symbols[i];
int x = g.w * (p->re - xymin) / (xymax - xymin);
int y = g.h - g.h * (p->im - xymin) / (xymax - xymin);
for ( int d=-2; d<=2; ++d ) {
for (int d = -2; d <= 2; ++d)
{
g.point(x + d, y);
g.point(x, y + d);
}
@ -174,14 +223,16 @@ namespace leansdr {
g.sync();
}
in.read(pixels_per_frame);
if ( ++phase >= decimation ) phase = 0;
if (++phase >= decimation)
phase = 0;
}
}
//private:
pipereader<complex<T>> in;
unsigned long phase;
gfx g;
void draw_begin() {
void draw_begin()
{
g.clear();
g.setfg(0, 255, 0);
g.line(g.w / 2, 0, g.w / 2, g.h);
@ -190,30 +241,46 @@ namespace leansdr {
};
template <typename T>
struct wavescope : runnable {
struct wavescope : runnable
{
T ymin, ymax;
unsigned long decimation;
float hgrid;
wavescope(scheduler *sch, pipebuf<T> &_in,
T _ymin, T _ymax, const char *_name = NULL)
: runnable(sch, _name ? _name : _in.name),
in(_in), ymin(_ymin), ymax(_ymax),
decimation(DEFAULT_GUI_DECIMATION),
g(sch, name), phase(0),
x(0) {
ymin(_ymin), ymax(_ymax),
decimation(DEFAULT_GUI_DECIMATION), hgrid(0),
in(_in),
phase(0), g(sch, name), x(0)
{
g.clear();
}
void run() {
while ( in.readable() >= g.w ) {
if ( ! phase ) plot(in.rd(), g.w);
void run()
{
while (in.readable() >= g.w)
{
if (!phase)
plot(in.rd(), g.w);
in.read(g.w);
if ( ++phase >= decimation ) phase = 0;
if (++phase >= decimation)
phase = 0;
}
}
void plot(T *p, int count) {
void plot(T *p, int count)
{
T *pend = p + count;
g.clear();
g.setfg(128, 128, 0);
g.line(0, g.h / 2, g.w - 1, g.h / 2);
if (hgrid)
{
for (float x = 0; x < g.w; x += hgrid)
g.line(x, 0, x, g.h - 1);
}
g.setfg(0, 255, 0);
for ( int x=0; p<pend; ++x,++p ) {
for (int x = 0; p < pend; ++x, ++p)
{
T v = *p;
g.point(x, g.h - 1 - (g.h - 1) * (v - ymin) / (ymax - ymin));
}
@ -229,14 +296,17 @@ namespace leansdr {
};
template <typename T>
struct slowmultiscope : runnable {
struct chanspec {
struct slowmultiscope : runnable
{
struct chanspec
{
pipebuf<T> *in;
const char *name, *format;
unsigned char rgb[3];
float scale;
float ymin, ymax;
enum flag {
enum flag
{
DEFAULT = 0,
ASYNC = 1, // Read whatever is available
COUNT = 2, // Display number of items read instead of value
@ -252,11 +322,14 @@ namespace leansdr {
const char *_name)
: runnable(sch, _name ? _name : "slowmultiscope"),
samples_per_pixel(1), sample_freq(1),
g(sch, name), t(0), x(0), total_samples(0) {
g(sch, name), t(0), x(0), total_samples(0)
{
chans = new channel[nspecs];
nchans = 0;
for ( int i=0; i<nspecs; ++i ) {
if ( specs[i].flags & chanspec::DISABLED ) continue;
for (int i = 0; i < nspecs; ++i)
{
if (specs[i].flags & chanspec::DISABLED)
continue;
chans[nchans].spec = specs[i];
chans[nchans].in = new pipereader<T>(*specs[i].in);
chans[nchans].accum = 0;
@ -264,29 +337,76 @@ namespace leansdr {
}
g.clear();
}
void run() {
// Read up to one pixel worth of data
void run()
{
// Asynchronous channels: Read all available data
for (channel *c = chans; c < chans + nchans; ++c)
{
if (c->spec.flags & chanspec::ASYNC)
run_channel(c, c->in->readable());
}
// Synchronous channels: Read up to one pixel worth of data
// between display syncs.
unsigned long count = samples_per_pixel;
for (channel *c = chans; c < chans + nchans; ++c)
if (!(c->spec.flags & chanspec::ASYNC))
count = min(count, c->in->readable());
for ( int n=count; n--; ) {
for ( channel *c=chans; c<chans+nchans; ++c ) {
int nr;
if ( c->spec.flags & chanspec::ASYNC )
// For async channels, read any and all available data.
nr = c->in->readable();
else
nr = 1;
for (int n = count; n--;)
{
for (channel *c = chans; c < chans + nchans; ++c)
if (!(c->spec.flags & chanspec::ASYNC))
run_channel(c, 1);
}
t += count;
g.show();
// Print instantatenous values as text
for (int i = 0; i < nchans; ++i)
{
channel *c = &chans[i];
g.setfg(c->spec.rgb[0], c->spec.rgb[1], c->spec.rgb[2]);
char text[256];
sprintf(text, c->spec.format, c->print_val);
g.transient_text(5, 20 + 16 * i, text);
}
run_gui();
if (t >= samples_per_pixel)
{
t = 0;
++x;
if (x >= g.w)
x = 0;
g.setfg(0, 0, 0);
g.line(x, 0, x, g.h - 1);
}
run_gui();
g.sync();
total_samples += count;
}
private:
int nchans;
struct channel
{
chanspec spec;
pipereader<T> *in;
float accum;
int prev_y;
float print_val;
} * chans;
void run_channel(channel *c, int nr)
{
g.setfg(c->spec.rgb[0], c->spec.rgb[1], c->spec.rgb[2]);
int y = -1;
while ( nr-- ) {
while (nr--)
{
float v = *c->in->rd() * c->spec.scale;
if (c->spec.flags & chanspec::COUNT)
++c->accum;
else if (c->spec.flags & chanspec::SUM)
c->accum += v;
else {
else
{
c->print_val = v;
float nv = (v - c->spec.ymin) / (c->spec.ymax - c->spec.ymin);
if (c->spec.flags & chanspec::WRAP)
@ -297,43 +417,27 @@ namespace leansdr {
}
// Display count/sum channels only when the cursor is about to move.
if ((c->spec.flags & (chanspec::COUNT | chanspec::SUM)) &&
t+1 >= samples_per_pixel ) {
t + 1 >= samples_per_pixel)
{
T v = c->accum;
y = g.h - 1 - g.h * (v - c->spec.ymin) / (c->spec.ymax - c->spec.ymin);
c->accum = 0;
c->print_val = v;
}
if ( y >= 0 ) {
if ( c->spec.flags & chanspec::LINE ) {
if ( x ) g.line(x-1, c->prev_y, x, y);
if (y >= 0)
{
if (c->spec.flags & chanspec::LINE)
{
if (x)
g.line(x - 1, c->prev_y, x, y);
c->prev_y = y;
} else
}
else
g.point(x, y);
}
}
g.show();
// Print instantatenous values as text
for ( int i=0; i<nchans; ++i ) {
channel *c = &chans[i];
g.setfg(c->spec.rgb[0], c->spec.rgb[1], c->spec.rgb[2]);
char text[256];
sprintf(text, c->spec.format, c->print_val);
g.transient_text(5, 20+16*i, text);
}
run_gui();
if ( ++t >= samples_per_pixel ) {
t = 0;
++x;
if ( x >= g.w ) x = 0;
g.setfg(0, 0, 0);
g.line(x, 0, x, g.h-1);
}
run_gui();
g.sync();
}
total_samples += count;
}
void run_gui() {
void run_gui()
{
g.events();
// Print cursor time
float ct = g.mx * samples_per_pixel / sample_freq;
@ -343,69 +447,84 @@ namespace leansdr {
g.setfg(255, 255, 255);
g.transient_text(g.w * 3 / 4, 20, text);
}
private:
int nchans;
struct channel {
chanspec spec;
pipereader<T> *in;
float accum;
int prev_y;
float print_val;
} *chans;
gfx g;
unsigned long t;
unsigned long t; // Time for synchronous channels
int x;
int total_samples;
};
template <typename T>
struct spectrumscope : runnable {
T ymax;
float amax;
struct spectrumscope : runnable
{
unsigned long size;
float amax;
unsigned long decimation;
spectrumscope(scheduler *sch, pipebuf<complex<T>> &_in,
T _max, const char *_name = NULL)
: runnable(sch, _name ? _name : _in.name),
ymax(_max), amax(_max),
size(4096), decimation(DEFAULT_GUI_DECIMATION),
in(_in), phase(0), g(sch, name), fft(NULL) {
size(4096), amax(_max / sqrtf(size)),
decimation(DEFAULT_GUI_DECIMATION),
in(_in),
nmarkers(0),
phase(0), g(sch, name), fft(NULL)
{
}
void run() {
while ( in.readable() >= size ) {
if ( ! phase ) do_fft(in.rd());
void mark_freq(float f)
{
if (nmarkers == MAX_MARKERS)
fail("Too many markers");
markers[nmarkers++] = f;
}
void run()
{
while (in.readable() >= size)
{
if (!phase)
do_fft(in.rd());
in.read(size);
if ( ++phase >= decimation ) phase = 0;
if (++phase >= decimation)
phase = 0;
}
}
private:
pipereader<complex<T>> in;
static const int MAX_MARKERS = 4;
float markers[MAX_MARKERS];
int nmarkers;
int phase;
gfx g;
cfft_engine<float> *fft;
void do_fft(complex<T> *input) {
void do_fft(complex<T> *input)
{
draw_begin();
if ( !fft || fft->n!=size ) {
if ( fft ) delete fft;
if (!fft || fft->n != size)
{
if (fft)
delete fft;
fft = new cfft_engine<float>(size);
}
complex<T> *pin = input, *pend = pin + size;
complex<float> data[size], *pout = data;
g.setfg(255, 0, 0);
for ( int x=0; pin<pend; ++pin,++pout,++x ) {
for (int x = 0; pin < pend; ++pin, ++pout, ++x)
{
pout->re = (float)pin->re;
pout->im = (float)pin->im;
// g.point(x, g.h/2-pout->re*g.h/2/ymax);
}
fft->inplace(data, true);
g.setfg(0, 255, 0);
for ( int i=0; i<size; ++i ) {
for (int i = 0; i < size; ++i)
{
int x = ((i < size / 2) ? i + size / 2 : i - size / 2) * g.w / size;
complex<float> v = data[i];;
complex<float> v = data[i];
;
float y = hypot(v.re, v.im);
g.line(x, g.h - 1, x, g.h - 1 - y * g.h / amax);
}
if ( g.buttons ) {
if (g.buttons)
{
char s[256];
float f = 2.4e6 * (g.mx - g.w / 2) / g.w;
sprintf(s, "%f", f);
@ -414,15 +533,23 @@ namespace leansdr {
g.show();
g.sync();
}
void draw_begin() {
void draw_begin()
{
g.clear();
g.setfg(255, 255, 255);
g.line(g.w / 2, 0, g.w / 2, g.h);
g.setfg(255, 0, 0);
for (int i = 0; i < nmarkers; ++i)
{
int x = g.w * (0.5 + markers[i]);
g.line(x, 0, x, g.h);
}
}
};
template <typename T>
struct rfscope : runnable {
struct rfscope : runnable
{
unsigned long size;
unsigned long decimation;
float Fs; // Sampling freq for display (Hz)
@ -438,15 +565,21 @@ namespace leansdr {
size(4096), decimation(DEFAULT_GUI_DECIMATION),
Fs(1), Fc(0), ncursors(0), hzoom(1),
db0(-25), dbrange(50), bw(0.05),
in(_in), phase(0), g(sch, name), fft(NULL), filtered(NULL) {
in(_in), phase(0), g(sch, name), fft(NULL), filtered(NULL)
{
}
void run() {
while ( in.readable() >= size ) {
if ( ! phase ) do_fft(in.rd());
void run()
{
while (in.readable() >= size)
{
if (!phase)
do_fft(in.rd());
in.read(size);
if ( ++phase >= decimation ) phase = 0;
if (++phase >= decimation)
phase = 0;
}
}
private:
pipereader<complex<T>> in;
int phase;
@ -454,33 +587,42 @@ namespace leansdr {
cfft_engine<float> *fft;
float *filtered;
void do_fft(complex<T> *input) {
void do_fft(complex<T> *input)
{
g.events();
draw_begin();
if ( !fft || fft->n!=size ) {
if ( fft ) delete fft;
if (!fft || fft->n != size)
{
if (fft)
delete fft;
fft = new cfft_engine<float>(size);
}
// Convert to complex<float> and transform
complex<T> *pin = input, *pend = pin + size;
complex<float> data[size], *pout = data;
for ( int x=0; pin<pend; ++pin,++pout,++x ) {
for (int x = 0; pin < pend; ++pin, ++pout, ++x)
{
pout->re = (float)pin->re;
pout->im = (float)pin->im;
}
fft->inplace(data, true);
float amp2[size];
for ( int i=0; i<size; ++i ) {
complex<float> &v = data[i];;
for (int i = 0; i < size; ++i)
{
complex<float> &v = data[i];
;
amp2[i] = (v.re * v.re + v.im * v.im) * size;
}
if ( ! filtered ) {
if (!filtered)
{
filtered = new float[size];
for ( int i=0; i<size; ++i ) filtered[i] = amp2[i];
for (int i = 0; i < size; ++i)
filtered[i] = amp2[i];
}
float bwcomp = 1 - bw;
g.setfg(0, 255, 0);
for ( int i=0; i<size; ++i ) {
for (int i = 0; i < size; ++i)
{
filtered[i] = amp2[i] * bw + filtered[i] * bwcomp;
float db = filtered[i] ? 10 * logf(filtered[i]) / logf(10) : db0;
int is = (i < size / 2) ? i : i - size;
@ -488,7 +630,8 @@ namespace leansdr {
int y = g.h - 1 - (db - db0) * g.h / dbrange;
g.line(x, g.h - 1, x, y);
}
if ( g.buttons ) {
if (g.buttons)
{
char s[256];
float freq = Fc + Fs * (g.mx - g.w / 2) / g.w / hzoom;
float val = db0 + (float)((g.h - 1) - g.my) * dbrange / g.h;
@ -498,18 +641,21 @@ namespace leansdr {
}
// Draw cursors
g.setfg(255, 255, 0);
for ( int i=0; i<ncursors; ++i ) {
for (int i = 0; i < ncursors; ++i)
{
int x = g.w / 2 + (cursors[i] - Fc) * hzoom * g.w / Fs;
g.line(x, 0, x, g.h - 1);
}
g.show();
g.sync();
}
void draw_begin() {
void draw_begin()
{
g.clear();
// dB scale
g.setfg(64, 64, 64);
for ( float db=floorf(db0); db<db0+dbrange; ++db ) {
for (float db = floorf(db0); db < db0 + dbrange; ++db)
{
int y = g.h - 1 - (db - db0) * g.h / dbrange;
g.line(0, y, g.w - 1, y);
}
@ -520,12 +666,15 @@ namespace leansdr {
};
template <typename T>
struct genscope : runnable {
struct render {
struct genscope : runnable
{
struct render
{
int x, y;
char dir; // 'h'orizontal or 'v'ertical
};
struct chanspec {
struct chanspec
{
pipebuf<T> *in; // NULL if disabled
render r;
};
@ -533,12 +682,17 @@ namespace leansdr {
const char *_name = NULL)
: runnable(sch, _name ? _name : "genscope"),
nchans(_nchans),
g(sch, name) {
g(sch, name)
{
chans = new channel[nchans];
for ( int i=0; i<nchans; ++i ) {
if ( ! specs[i].in ) {
for (int i = 0; i < nchans; ++i)
{
if (!specs[i].in)
{
chans[i].in = NULL;
} else {
}
else
{
chans[i].spec = specs[i];
chans[i].in = new pipereader<T>(*specs[i].in);
}
@ -546,16 +700,20 @@ namespace leansdr {
g.clear();
gettimeofday(&tv, NULL);
}
struct channel {
struct channel
{
chanspec spec;
pipereader<T> *in;
} * chans;
int nchans;
struct timeval tv;
void run() {
void run()
{
g.setfg(0, 255, 0);
for ( channel *pc=chans; pc<chans+nchans; ++pc ) {
if ( ! pc->in ) continue;
for (channel *pc = chans; pc < chans + nchans; ++pc)
{
if (!pc->in)
continue;
int n = pc->in->readable();
T last = pc->in->rd()[n - 1];
pc->in->read(n);
@ -572,7 +730,8 @@ namespace leansdr {
struct timeval newtv;
gettimeofday(&newtv, NULL);
int dt = (newtv.tv_sec - tv.tv_sec) * 1000 + (newtv.tv_usec - tv.tv_usec) / 1000;
if ( dt > 100 ) {
if (dt > 100)
{
fprintf(stderr, "#");
g.show();
g.sync();
@ -580,12 +739,13 @@ namespace leansdr {
tv = newtv;
}
}
private:
gfx g;
};
#endif // GUI
} // namespace
} // namespace leansdr
#endif // LEANSDR_GUI_H

View File

@ -1,3 +1,19 @@
// This file is part of LeanSDR Copyright (C) 2016-2018 <pabr@pabr.org>.
// See the toplevel README for more information.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef LEANSDR_HDLC_H
#define LEANSDR_HDLC_H
@ -12,18 +28,13 @@ struct hdlc_dec
{
hdlc_dec(int _minframesize, // Including CRC, excluding HDLC flags.
int _maxframesize, bool _invert) :
minframesize(_minframesize),
int _maxframesize,
bool _invert) : minframesize(_minframesize),
maxframesize(_maxframesize),
invertmask(_invert ? 0xff : 0),
framebuf(new u8[maxframesize]),
debug(false)
{
byte_out = 0;
nbits_out = 0;
framesize = 0;
crc16 = 0;
reset();
}
@ -45,20 +56,22 @@ struct hdlc_dec
// Return number of checksum errors in *fcs_errors.
// *ppin will have increased by at least 1 (unless count==0).
u8 *decode(u8 **ppin, int count, int *pdatasize, int *hdlc_errors,
int *fcs_errors)
u8 *decode(u8 **ppin, int count, int *pdatasize, int *hdlc_errors, int *fcs_errors)
{
*hdlc_errors = 0;
*fcs_errors = 0;
*pdatasize = -1;
u8 *pin = *ppin, *pend = pin + count;
for (; pin < pend; ++pin)
{
u8 byte_in = (*pin) ^ invertmask;
for (int bits = 8; bits--; byte_in <<= 1)
{
u8 bit_in = byte_in & 128;
shiftreg = (shiftreg >> 1) | bit_in;
if (!inframe)
{
if (shiftreg == 0x7e)
@ -87,8 +100,7 @@ struct hdlc_dec
{
// Checksum
crc16 ^= 0xffff;
if (framesize < 2 || framesize < minframesize
|| crc16 != crc16_check)
if (framesize < 2 || framesize < minframesize || crc16 != crc16_check)
{
if (debug)
fprintf(stderr, "!");
@ -143,6 +155,7 @@ struct hdlc_dec
return framebuf;
}
}
*ppin = pin;
return NULL;
}
@ -163,12 +176,16 @@ private:
static const u16 crc16_init = 0xffff;
static const u16 crc16_poly = 0x8408; // 0x1021 MSB-first
static const u16 crc16_check = 0x0f47;
void crc16_byte(u8 data)
{
crc16 ^= data;
for (int bit = 8; bit--;)
{
crc16 = (crc16 & 1) ? (crc16 >> 1) ^ crc16_poly : (crc16 >> 1);
}
}
public:
bool debug;
@ -179,8 +196,7 @@ public:
struct hdlc_sync : runnable
{
hdlc_sync(scheduler *sch,
pipebuf<u8> &_in, // Packed bits
hdlc_sync(scheduler *sch, pipebuf<u8> &_in, // Packed bits
pipebuf<u8> &_out, // Bytes
int _minframesize, // Including CRC, excluding HDLC flags.
int _maxframesize,
@ -189,42 +205,48 @@ struct hdlc_sync: runnable
pipebuf<int> *_framecount_out = NULL,
pipebuf<int> *_fcserrcount_out = NULL,
pipebuf<int> *_hdlcbytecount_out = NULL,
pipebuf<int> *_databytecount_out = NULL) :
runnable(sch, "hdlc_sync"), minframesize(_minframesize), maxframesize(
_maxframesize), chunk_size(maxframesize + 2), in(_in), out(
_out, _maxframesize + chunk_size), lock_out(
opt_writer(_lock_out)), framecount_out(
opt_writer(_framecount_out)), fcserrcount_out(
opt_writer(_fcserrcount_out)), hdlcbytecount_out(
opt_writer(_hdlcbytecount_out)), databytecount_out(
opt_writer(_databytecount_out)), cur_sync(0), resync_phase(
0), lock_state(false), resync_period(32), header16(false)
pipebuf<int> *_databytecount_out = NULL) : runnable(sch, "hdlc_sync"),
minframesize(_minframesize),
maxframesize(_maxframesize),
chunk_size(maxframesize + 2),
in(_in),
out(_out, _maxframesize + chunk_size),
lock_out(opt_writer(_lock_out)),
framecount_out(opt_writer(_framecount_out)),
fcserrcount_out(opt_writer(_fcserrcount_out)),
hdlcbytecount_out(opt_writer(_hdlcbytecount_out)),
databytecount_out(opt_writer(_databytecount_out)),
cur_sync(0),
resync_phase(0),
lock_state(false),
resync_period(32),
header16(false)
{
for (int s = 0; s < NSYNCS; ++s)
{
syncs[s].dec = new hdlc_dec(minframesize, maxframesize, s != 0);
for (int h = 0; h < NERRHIST; ++h)
syncs[s].errhist[h] = 0;
}
syncs[cur_sync].dec->debug = sch->debug;
errslot = 0;
}
void run()
{
if (!opt_writable(lock_out) || !opt_writable(framecount_out)
|| !opt_writable(fcserrcount_out)
|| !opt_writable(hdlcbytecount_out)
|| !opt_writable(databytecount_out))
if (!opt_writable(lock_out) || !opt_writable(framecount_out) || !opt_writable(fcserrcount_out) || !opt_writable(hdlcbytecount_out) || !opt_writable(databytecount_out))
{
return;
}
bool previous_lock_state = lock_state;
int fcserrcount = 0, framecount = 0;
int hdlcbytecount = 0, databytecount = 0;
// Note: hdlc_dec may already hold one frame ready for output.
while ((long) in.readable() >= chunk_size
&& (long) out.writable() >= maxframesize + chunk_size)
while ((long)in.readable() >= chunk_size && (long)out.writable() >= maxframesize + chunk_size)
{
if (!resync_phase)
{
@ -233,14 +255,15 @@ struct hdlc_sync: runnable
{
if (s != cur_sync)
syncs[s].dec->reset();
syncs[s].errhist[errslot] = 0;
for (u8 *pin = in.rd(), *pend = pin + chunk_size;
pin < pend;)
for (u8 *pin = in.rd(), *pend = pin + chunk_size; pin < pend;)
{
int datasize, hdlc_errors, fcs_errors;
u8 *f = syncs[s].dec->decode(&pin, pend - pin,
&datasize, &hdlc_errors, &fcs_errors);
u8 *f = syncs[s].dec->decode(&pin, pend - pin, &datasize, &hdlc_errors, &fcs_errors);
syncs[s].errhist[errslot] += hdlc_errors;
if (s == cur_sync)
{
if (f)
@ -250,32 +273,39 @@ struct hdlc_sync: runnable
databytecount += datasize;
++framecount;
}
fcserrcount += fcs_errors;
framecount += fcs_errors;
}
}
}
errslot = (errslot + 1) % NERRHIST;
// Switch to another sync option ?
// Compare total error counts over about NERRHIST frames.
int total_errors[NSYNCS];
for (int s = 0; s < NSYNCS; ++s)
{
total_errors[s] = 0;
for (int h = 0; h < NERRHIST; ++h)
total_errors[s] += syncs[s].errhist[h];
}
int best = cur_sync;
for (int s = 0; s < NSYNCS; ++s)
if (total_errors[s] < total_errors[best])
best = s;
if (best != cur_sync)
{
lock_state = false;
if (sch->debug)
fprintf(stderr, "[%d:%d->%d:%d]", cur_sync,
total_errors[cur_sync], best,
total_errors[best]);
fprintf(stderr, "[%d:%d->%d:%d]", cur_sync, total_errors[cur_sync], best, total_errors[best]);
// No verbose messages on candidate syncs
syncs[cur_sync].dec->debug = false;
cur_sync = best;
@ -288,8 +318,8 @@ struct hdlc_sync: runnable
for (u8 *pin = in.rd(), *pend = pin + chunk_size; pin < pend;)
{
int datasize, hdlc_errors, fcs_errors;
u8 *f = syncs[cur_sync].dec->decode(&pin, pend - pin,
&datasize, &hdlc_errors, &fcs_errors);
u8 *f = syncs[cur_sync].dec->decode(&pin, pend - pin, &datasize, &hdlc_errors, &fcs_errors);
if (f)
{
lock_state = true;
@ -297,18 +327,22 @@ struct hdlc_sync: runnable
databytecount += datasize;
++framecount;
}
fcserrcount += fcs_errors;
framecount += fcs_errors;
}
} // resync_phase
in.read(chunk_size);
hdlcbytecount += chunk_size;
if (++resync_phase >= resync_period)
resync_phase = 0;
} // Work to do
if (lock_state != previous_lock_state)
opt_write(lock_out, lock_state ? 1 : 0);
opt_write(framecount_out, framecount);
opt_write(fcserrcount_out, fcserrcount);
opt_write(hdlcbytecount_out, hdlcbytecount);
@ -324,6 +358,7 @@ private:
out.write(size >> 8);
out.write(size & 255);
}
memcpy(out.wr(), f, size);
out.written(size);
opt_write(framecount_out, 1);
@ -338,21 +373,24 @@ private:
pipewriter<int> *hdlcbytecount_out, *databytecount_out;
static const int NSYNCS = 2; // Two possible polarities
static const int NERRHIST = 2; // Compare error counts over two frames
struct
{
hdlc_dec *dec;
int errhist[NERRHIST];
} syncs[NSYNCS];
int errslot;
int cur_sync;
int resync_phase;
bool lock_state;
public:
int resync_period;
bool header16; // Output length prefix
};
// hdlc_sync
}// namespace
} // namespace leansdr
#endif // LEANSDR_HDLC_H

View File

@ -1,15 +1,33 @@
// This file is part of LeanSDR Copyright (C) 2016-2018 <pabr@pabr.org>.
// See the toplevel README for more information.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef LEANSDR_IESS_H
#define LEANSDR_IESS_H
#include "leansdr/framework.h"
namespace leansdr {
namespace leansdr
{
// SELF-SYNCHRONIZING DESCRAMBLER
// Per ETSI TR 192 figure 8 (except Q20/ not connected to CLOCK).
// This implementation operates on packed bits, MSB first.
struct etr192_descrambler : runnable {
struct etr192_descrambler : runnable
{
etr192_descrambler(scheduler *sch,
pipebuf<u8> &_in, // Packed scrambled bits
pipebuf<u8> &_out) // Packed bits
@ -19,12 +37,15 @@ namespace leansdr {
{
}
void run() {
void run()
{
int count = min(in.readable(), out.writable());
for (u8 *pin = in.rd(), *pend = pin + count, *pout = out.wr();
pin<pend; ++pin,++pout) {
pin < pend; ++pin, ++pout)
{
u8 byte_in = *pin, byte_out = 0;
for ( int b=8; b--; byte_in<<=1 ) {
for (int b = 8; b--; byte_in <<= 1)
{
// Levels before clock transition
int bit_in = (byte_in & 128) ? 1 : 0;
int reset_counter = (shiftreg ^ (shiftreg >> 8)) & 1;
@ -53,6 +74,6 @@ namespace leansdr {
u8 counter; // 5 bits
}; // etr192_descrambler
} // namespace
} // namespace leansdr
#endif // LEANSDR_IESS_H

View File

@ -1,64 +0,0 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRBASE_UTIL_INCREMENTALARRAY_H_
#define SDRBASE_UTIL_INCREMENTALARRAY_H_
#include <stdint.h>
namespace leansdr
{
template<typename T>
class IncrementalArray
{
public:
T *m_array;
IncrementalArray();
~IncrementalArray();
void allocate(uint32_t size);
private:
uint32_t m_size;
};
template<typename T>
IncrementalArray<T>::IncrementalArray() :
m_array(0),
m_size(0)
{
}
template<typename T>
IncrementalArray<T>::~IncrementalArray()
{
if (m_array) { delete[] m_array; }
}
template<typename T>
void IncrementalArray<T>::allocate(uint32_t size)
{
if (size <= m_size) { return; }
if (m_array) { delete[] m_array; }
m_array = new T[size];
m_size = size;
}
} // namespace
#endif /* SDRBASE_UTIL_INCREMENTALARRAY_H_ */

View File

@ -0,0 +1,520 @@
// This file is part of LeanSDR Copyright (C) 2016-2018 <pabr@pabr.org>.
// See the toplevel README for more information.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef LEANSDR_LDPC_H
#define LEANSDR_LDPC_H
#define lfprintf(...) \
{ \
}
namespace leansdr
{
// LDPC sparse matrix specified like in the DVB-S2 standard
// Taddr must be wide enough to index message bits and address bits.
template <typename Taddr>
struct ldpc_table
{
// TBD Save space
static const int MAX_ROWS = 162; // 64800 * (9/10) / 360
static const int MAX_COLS = 13;
int q;
int nrows;
struct row
{
int ncols;
Taddr cols[MAX_COLS];
} rows[MAX_ROWS];
};
// LDPC ENGINE
// SOFTBITs can be hard (e.g. bool) or soft (e.g. llr_t).
// They are stored as SOFTWORDs containing SWSIZE SOFTBITs.
// See interface in softword.h.
template <typename SOFTBIT, typename SOFTWORD, int SWSIZE, typename Taddr>
struct ldpc_engine
{
ldpc_engine()
: vnodes(NULL), cnodes(NULL)
{
}
// vnodes: Value/variable nodes (message bits)
// cnodes: Check nodes (parity bits)
int k; // Message size in bits
int n; // Codeword size in bits
struct node
{
Taddr *edges;
int nedges;
static const int CHUNK = 4; // Grow edges[] in steps of CHUNK.
void append(Taddr a)
{
if (nedges % CHUNK == 0)
{ // Full ?
edges = (Taddr *)realloc(edges, (nedges + CHUNK) * sizeof(Taddr));
if (!edges)
fatal("realloc");
}
edges[nedges++] = a;
}
};
node *vnodes; // [k]
node *cnodes; // [n-k]
// Initialize from a S2-style table.
ldpc_engine(const ldpc_table<Taddr> *table, int _k, int _n)
: k(_k), n(_n)
{
// Sanity checks
if (360 % SWSIZE)
fatal("Bad LDPC word size");
if (k % SWSIZE)
fatal("Bad LDPC k");
if (n % SWSIZE)
fatal("Bad LDPC n");
if (k != table->nrows * 360)
fatal("Bad table");
int n_k = n - k;
if (table->q * 360 != n_k)
fatal("Bad q");
vnodes = new node[k];
memset(vnodes, 0, sizeof(node) * k);
cnodes = new node[n_k];
memset(cnodes, 0, sizeof(node) * n_k);
// Expand the graph.
int m = 0;
// Iterate over rows
for (const typename ldpc_table<Taddr>::row *prow = table->rows;
prow < table->rows + table->nrows;
++prow)
{
// Process 360 bits per row.
int q = table->q;
int qoffs = 0;
for (int mw = 360; mw--; ++m, qoffs += q)
{
const Taddr *pa = prow->cols;
for (int nc = prow->ncols; nc--; ++pa)
{
int a = (int)*pa + qoffs;
if (a >= n_k)
a -= n_k; // Modulo n-k. Note qoffs<360*q.
if (a >= n_k)
fail("Invalid LDPC table");
vnodes[m].append(a);
cnodes[a].append(m);
}
}
}
}
void print_node_stats()
{
int nedges = count_edges(vnodes, k);
fprintf(stderr, "LDPC(%5d,%5d)(%.2f)"
" %5.2f edges/vnode, %5.2f edges/cnode\n",
k, n - k, (float)k / n, (float)nedges / k, (float)nedges / (n - k));
}
int count_edges(node *nodes, int nnodes)
{
int c = 0;
for (int i = 0; i < nnodes; ++i)
c += nodes[i].nedges;
return c;
}
// k: Message size in bits
// n: Codeword size in bits
// integrate: Optional S2-style post-processing
#if 0
void encode_hard(const ldpc_table<Taddr> *table, const uint8_t *msg,
int k, int n, uint8_t *parity, bool integrate=true) {
// Sanity checks
if ( 360 % SWSIZE ) fatal("Bad LDPC word size");
if ( k % SWSIZE ) fatal("Bad LDPC k");
if ( n % SWSIZE ) fatal("Bad LDPC n");
if ( k != table->nrows*360 ) fatal("Bad table");
int n_k = n - k;
if ( table->q*360 != n_k ) fatal("Bad q");
for ( int i=0; i<n_k/SWSIZE; ++i ) softword_zero(&parity[i]);
// Iterate over rows
for ( const typename ldpc_table<Taddr>::row *prow = table->rows; // quirk
prow < table->rows+table->nrows;
++prow ) {
// Process 360 bits per row, in words of SWSIZE bits
int q = table->q;
int qoffs = 0;
for ( int mw=360/SWSIZE; mw--; ++msg ) {
SOFTWORD msgword = *msg;
for ( int wbit=0; wbit<SWSIZE; ++wbit,qoffs+=q ) {
SOFTBIT msgbit = softword_get(msgword, wbit);
if ( ! msgbit ) continue; // TBD Generic soft version
const Taddr *pa = prow->cols;
for ( int nc=prow->ncols; nc--; ++pa ) {
// Don't wrap modulo range of Taddr
int a = (int)*pa + qoffs;
// Note: qoffs < 360*q=n-k
if ( a >= n_k ) a -= n_k; // TBD not predictable
softwords_flip(parity, a);
}
}
}
}
if ( integrate )
integrate_bits(parity, parity, n_k/SWSIZE);
}
#endif
void encode(const ldpc_table<Taddr> *table, const SOFTWORD *msg,
int k, int n, SOFTWORD *parity, int integrate = true)
{
// Sanity checks
if (360 % SWSIZE)
fatal("Bad LDPC word size");
if (k % SWSIZE)
fatal("Bad LDPC k");
if (n % SWSIZE)
fatal("Bad LDPC n");
if (k != table->nrows * 360)
fatal("Bad table");
int n_k = n - k;
if (table->q * 360 != n_k)
fatal("Bad q");
for (int i = 0; i < n_k / SWSIZE; ++i)
softword_zero(&parity[i]);
// Iterate over rows
for (const typename ldpc_table<Taddr>::row *prow = table->rows; // quirk
prow < table->rows + table->nrows;
++prow)
{
// Process 360 bits per row, in words of SWSIZE bits
int q = table->q;
int qoffs = 0;
for (int mw = 360 / SWSIZE; mw--; ++msg)
{
SOFTWORD msgword = *msg;
for (int wbit = 0; wbit < SWSIZE; ++wbit, qoffs += q)
{
SOFTBIT msgbit = softword_get(msgword, wbit);
if (!softbit_harden(msgbit))
continue;
const Taddr *pa = prow->cols;
for (int nc = prow->ncols; nc--; ++pa)
{
int a = (int)*pa + qoffs;
// Note: qoffs < 360*q=n-k
if (a >= n_k)
a -= n_k; // TBD not predictable
softwords_flip(parity, a);
}
}
}
}
if (integrate)
integrate_bits(parity, parity, n_k / SWSIZE);
}
// Flip bits connected to parity errors, one at a time,
// as long as things improve and max_bitflips is not exceeded.
// cw: codeword (k value bits followed by n-k check bits)
static const int PPCM = 39;
typedef int64_t score_t;
score_t compute_scores(SOFTWORD *m, SOFTWORD *p, SOFTWORD *q, int nc,
score_t *score, int k)
{
int total = 0;
memset(score, 0, k * sizeof(*score));
for (int c = 0; c < nc; ++c)
{
SOFTBIT err = softwords_xor(p, q, c);
if (softbit_harden(err))
{
Taddr *pe = cnodes[c].edges;
for (int e = cnodes[c].nedges; e--; ++pe)
{
int v = *pe;
int s = err * softwords_weight<SOFTBIT, SOFTWORD>(m, v) * PPCM / vnodes[v].nedges;
//fprintf(stderr, "c[%d] bad => v[%d] += %d (%d*%d)\n",
///c, v, s, err, softwords_weight<SOFTBIT,SOFTWORD>(m,*pe));
score[v] += s;
total += s;
}
}
}
return total;
}
int decode_bitflip(const ldpc_table<Taddr> *table, SOFTWORD *cw,
int k, int n,
int max_bitflips)
{
if (!vnodes)
fail("LDPC graph not initialized");
int n_k = n - k;
// Compute the expected check bits (without the final mixing)
SOFTWORD expected[n_k / SWSIZE];
encode(table, cw, k, n, expected, false);
// Reverse the integrator mixing from the received check bits
SOFTWORD received[n_k / SWSIZE];
diff_bits(cw + k / SWSIZE, received, n_k / SWSIZE);
// Compute initial scores
score_t score[k];
score_t tots = compute_scores(cw, expected, received, n_k, score, k);
lfprintf(stderr, "Initial score %d\n", (int)tots);
int nflipped = 0;
score_t score_threshold;
{
SOFTBIT one;
softbit_set(&one, true);
score_threshold = (int)one * 2;
}
bool progress = true;
while (progress && nflipped < max_bitflips)
{
progress = false;
// Try to flip parity bits.
// Due to differential decoding, they appear as consecutive errors.
SOFTBIT prev_err = softwords_xor(expected, received, 0);
for (int b = 0; b < n - k - 1; ++b)
{
prev_err = softwords_xor(expected, received, b); //TBD
SOFTBIT err = softwords_xor(expected, received, b + 1);
if (softbit_harden(prev_err) && softbit_harden(err))
{
lfprintf(stderr, "flip parity %d\n", b);
softwords_flip(received, b);
softwords_flip(received, b + 1);
++nflipped; // Counts as one flip before differential decoding.
progress = true;
int dtot = 0;
// Depenalize adjacent message bits.
{
Taddr *pe = cnodes[b].edges;
for (int e = cnodes[b].nedges; e--; ++pe)
{
int d = prev_err * softwords_weight<SOFTBIT, SOFTWORD>(cw, *pe) * PPCM / vnodes[*pe].nedges;
score[*pe] -= d;
dtot -= d;
}
}
{
Taddr *pe = cnodes[b + 1].edges;
for (int e = cnodes[b + 1].nedges; e--; ++pe)
{
int d = err * softwords_weight<SOFTBIT, SOFTWORD>(cw, *pe) * PPCM / vnodes[*pe].nedges;
score[*pe] -= d;
dtot -= d;
}
}
tots += dtot;
#if 1
// Also update the codeword in-place.
// TBD Useful for debugging only.
softwords_flip(cw, k + b);
#endif
// TBD version soft. err = ! err;
}
prev_err = err;
} // c nodes
score_t maxs = -(1 << 30);
for (int v = 0; v < k; ++v)
if (score[v] > maxs)
maxs = score[v];
if (!maxs)
break;
lfprintf(stderr, "maxs %d\n", (int)maxs);
// Try to flip each message bits with maximal score
for (int v = 0; v < k; ++v)
{
if (score[v] < score_threshold)
continue;
// if ( score[v] < maxs*9/10 ) continue;
if (score[v] < maxs - 4)
continue;
lfprintf(stderr, " flip %d score=%d\n", (int)v, (int)score[v]);
// Update expected parities and scores that depend on them.
score_t dtot = 0;
for (int commit = 0; commit <= 1; ++commit)
{
Taddr *pe = vnodes[v].edges;
for (int e = vnodes[v].nedges; e--; ++pe)
{
Taddr c = *pe;
SOFTBIT was_bad = softwords_xor(expected, received, c);
if (softbit_harden(was_bad))
{
Taddr *pe = cnodes[c].edges;
for (int e = cnodes[c].nedges; e--; ++pe)
{
int d = was_bad * softwords_weight<SOFTBIT, SOFTWORD>(cw, *pe) * PPCM / vnodes[*pe].nedges;
if (commit)
score[*pe] -= d;
else
dtot -= d;
}
}
softwords_flip(expected, c);
SOFTBIT is_bad = softwords_xor(expected, received, c);
if (softbit_harden(is_bad))
{
Taddr *pe = cnodes[c].edges;
for (int e = cnodes[c].nedges; e--; ++pe)
{
int d = is_bad * softwords_weight<SOFTBIT, SOFTWORD>(cw, *pe) * PPCM / vnodes[*pe].nedges;
if (commit)
score[*pe] += d;
else
dtot += d;
}
}
if (!commit)
softwords_flip(expected, c);
}
if (!commit)
{
if (dtot >= 0)
{
lfprintf(stderr, " abort %d\n", v);
break; // Next v
}
}
else
{
softwords_flip(cw, v);
++nflipped;
tots += dtot;
progress = true;
v = k - 1; // Force exit to update maxs ?
}
} // commit
} // v
lfprintf(stderr, "progress %d\n", progress);
#if 0
fprintf(stderr, "CHECKING TOTS INCREMENT (slow) %d\n", tots);
score_t tots2 = compute_scores(cw, expected, received, n_k, score, k);
if ( tots2 != tots ) fail("bad tots update");
#endif
}
return nflipped;
}
// EN 302 307-1 5.3.2.1 post-processing of parity bits.
// In-place allowed.
#if 1
static void integrate_bits(const SOFTWORD *in, SOFTWORD *out, int nwords)
{
SOFTBIT sum;
softbit_clear(&sum);
for (int i = 0; i < nwords; ++i)
{
SOFTWORD w = in[i];
for (int b = 0; b < SWSIZE; ++b)
{
sum = softbit_xor(sum, softword_get(w, b));
softword_write(w, b, sum);
}
out[i] = w;
}
}
#else
// Optimized for hard_sb
static void integrate_bits(const uint8_t *in, uint8_t *out, int nwords)
{
// TBD Optimize
uint8_t prev = 0;
for (int i = 0; i < nwords; ++i)
{
uint8_t c = in[i];
for (int j = SWSIZE; j--;)
{
c ^= prev << j;
prev = (c >> j) & 1;
}
out[i] = c;
}
}
#endif
// Undo EN 302 307-1 5.3.2.1, post-processing of parity bits.
// In-place allowed.
#if 1
static void diff_bits(const SOFTWORD *in, SOFTWORD *out, int nwords)
{
SOFTBIT prev;
softbit_clear(&prev);
for (int i = 0; i < nwords; ++i, ++in, ++out)
{
SOFTWORD w = *in;
for (int b = 0; b < SWSIZE; ++b)
{
SOFTBIT n = softword_get(w, b);
softword_write(w, b, softbit_xor(prev, n));
prev = n;
}
*out = w;
}
}
#else
// Optimized for hard_sb
static void diff_bits(const uint8_t *in, uint8_t *out, int nwords)
{
uint8_t prev = 0;
for (int i = 0; i < nwords; ++i)
{
uint8_t c = in[i];
out[i] = c ^ (prev | (c >> 1));
prev = (c & 1) << (SWSIZE - 1);
}
}
#endif
}; // ldpc_engine
} // namespace leansdr
#endif // LEANSDR_LDPC_H

View File

@ -0,0 +1,56 @@
#include "math.h"
namespace leansdr
{
int hamming_weight(uint8_t x)
{
static const int lut[16] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4};
return lut[x & 15] + lut[x >> 4];
}
int hamming_weight(uint16_t x)
{
return hamming_weight((uint8_t)x) + hamming_weight((uint8_t)(x >> 8));
}
int hamming_weight(uint32_t x)
{
return hamming_weight((uint16_t)x) + hamming_weight((uint16_t)(x >> 16));
}
int hamming_weight(uint64_t x)
{
return hamming_weight((uint32_t)x) + hamming_weight((uint32_t)(x >> 32));
}
unsigned char parity(uint8_t x)
{
x ^= x >> 4;
return (0x6996 >> (x & 15)) & 1; // 16-entry look-up table
}
unsigned char parity(uint16_t x)
{
return parity((uint8_t)(x ^ (x >> 8)));
}
unsigned char parity(uint32_t x)
{
return parity((uint16_t)(x ^ (x >> 16)));
}
unsigned char parity(uint64_t x)
{
return parity((uint32_t)(x ^ (x >> 32)));
}
int log2i(uint64_t x)
{
int n = -1;
for (; x; ++n, x >>= 1)
;
return n;
}
} // leansdr

View File

@ -1,99 +1,155 @@
// This file is part of LeanSDR Copyright (C) 2016-2018 <pabr@pabr.org>.
// See the toplevel README for more information.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef LEANSDR_MATH_H
#define LEANSDR_MATH_H
#define _USE_MATH_DEFINES
#include <math.h>
#include <stdint.h>
namespace leansdr {
namespace leansdr
{
template <typename T>
struct complex {
struct complex
{
T re, im;
complex() : re(0), im(0) { }
complex() {}
complex(T x) : re(x), im(0) {}
complex(T x, T y) : re(x), im(y) {}
inline void operator +=(const complex<T> &x) { re+=x.re; im+=x.im; }
inline void operator+=(const complex<T> &x)
{
re += x.re;
im += x.im;
}
inline void operator*=(const complex<T> &c)
{
T tre = re * c.re - im * c.im;
im = re * c.im + im * c.re;
re = tre;
}
inline void operator*=(const T &k)
{
re *= k;
im *= k;
}
};
template <typename T>
complex<T> operator +(const complex<T> &a, const complex<T> &b) {
complex<T> operator+(const complex<T> &a, const complex<T> &b)
{
return complex<T>(a.re + b.re, a.im + b.im);
}
template <typename T>
complex<T> operator *(const complex<T> &a, const complex<T> &b) {
complex<T> operator*(const complex<T> &a, const complex<T> &b)
{
return complex<T>(a.re * b.re - a.im * b.im, a.re * b.im + a.im * b.re);
}
template <typename T>
complex<T> operator *(const complex<T> &a, const T &k) {
complex<T> operator*(const complex<T> &a, const T &k)
{
return complex<T>(a.re * k, a.im * k);
}
template <typename T>
complex<T> operator *(const T &k, const complex<T> &a) {
complex<T> operator*(const T &k, const complex<T> &a)
{
return complex<T>(k * a.re, k * a.im);
}
template <typename T>
T dotprod(const T *u, const T *v, int n)
{
T acc = 0;
while (n--)
acc += (*u++) * (*v++);
return acc;
}
template <typename T>
inline T cnorm2(const complex<T> &u)
{
return u.re * u.re + u.im * u.im;
}
template <typename T>
T cnorm2(const complex<T> *p, int n)
{
T res = 0;
for (; n--; ++p)
res += cnorm2(*p);
return res;
}
// Return conj(u)*v
template <typename T>
inline complex<T> conjprod(const complex<T> &u, const complex<T> &v)
{
return complex<T>(u.re * v.re + u.im * v.im,
u.re * v.im - u.im * v.re);
}
// Return sum(conj(u[i])*v[i])
template <typename T>
complex<T> conjprod(const complex<T> *u, const complex<T> *v, int n)
{
complex<T> acc = 0;
while (n--)
acc += conjprod(*u++, *v++);
return acc;
}
// TBD Optimize with dedicated instructions
inline int hamming_weight(uint8_t x) {
static const int lut[16] = { 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4 };
return lut[x&15] + lut[x>>4];
}
inline int hamming_weight(uint16_t x) {
return hamming_weight((uint8_t)x)
+ hamming_weight((uint8_t)(x>>8));
}
inline int hamming_weight(uint32_t x) {
return hamming_weight((uint16_t)x)
+ hamming_weight((uint16_t)(x>>16));
}
inline int hamming_weight(uint64_t x) {
return hamming_weight((uint32_t)x)
+ hamming_weight((uint32_t)(x>>32));
}
inline unsigned char parity(uint8_t x) {
x ^= x>>4;
return (0x6996 >> (x&15)) & 1; // 16-entry look-up table
}
inline unsigned char parity(uint16_t x) {
return parity((uint8_t)(x^(x>>8)));
}
inline unsigned char parity(uint32_t x) {
return parity((uint16_t)(x^(x>>16)));
}
inline unsigned char parity(uint64_t x) {
return parity((uint32_t)(x^(x>>32)));
}
inline int log2i(uint64_t x) {
int n = -1;
for ( ; x; ++n,x>>=1 ) ;
return n;
}
int hamming_weight(uint8_t x);
int hamming_weight(uint16_t x);
int hamming_weight(uint32_t x);
int hamming_weight(uint64_t x);
unsigned char parity(uint8_t x);
unsigned char parity(uint16_t x);
unsigned char parity(uint32_t x);
unsigned char parity(uint64_t x);
int log2i(uint64_t x);
// Pre-computed sin/cos for 16-bit angles
struct trig16 {
struct trig16
{
complex<float> lut[65536]; // TBD static and shared
trig16() {
for ( int a=0; a<65536; ++a ) {
trig16()
{
for (int a = 0; a < 65536; ++a)
{
float af = a * 2 * M_PI / 65536;
lut[a].re = cosf(af);
lut[a].im = sinf(af);
}
}
inline const complex<float> &expi(uint16_t a) const {
inline const complex<float> &expi(uint16_t a) const
{
return lut[a];
}
// a must fit in a int32_t, otherwise behaviour is undefined
inline const complex<float> &expi(float a) const {
inline const complex<float> &expi(float a) const
{
return expi((uint16_t)(int16_t)(int32_t)a);
}
};
} // namespace
} // namespace leansdr
#endif // LEANSDR_MATH_H

View File

@ -1,3 +1,19 @@
// This file is part of LeanSDR Copyright (C) 2016-2018 <pabr@pabr.org>.
// See the toplevel README for more information.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef LEANSDR_RS_H
#define LEANSDR_RS_H
@ -34,10 +50,7 @@ struct gf2x_p
gf2x_p()
{
if (ALPHA != 2)
{
fail("gf2x_p::gf2x_p", "alpha!=2 not implemented");
return;
}
fail("alpha!=2 not implemented");
// Precompute log and exp tables.
Tp alpha_i = 1;
for (Tp i = 0; i < (1 << N); ++i)
@ -51,14 +64,8 @@ struct gf2x_p
}
}
static const Te alpha = ALPHA;
inline Te add(Te x, Te y)
{
return x ^ y;
} // Addition modulo 2
inline Te sub(Te x, Te y)
{
return x ^ y;
} // Subtraction modulo 2
inline Te add(Te x, Te y) { return x ^ y; } // Addition modulo 2
inline Te sub(Te x, Te y) { return x ^ y; } // Subtraction modulo 2
inline Te mul(Te x, Te y)
{
if (!x || !y)
@ -77,14 +84,9 @@ struct gf2x_p
// if ( ! x ) fail("inv");
return lut_exp[((1 << N) - 1) - lut_log[x]];
}
inline Te exp(Te x)
{
return lut_exp[x];
}
inline Te log(Te x)
{
return lut_log[x];
}
inline Te exp(Te x) { return lut_exp[x]; }
inline Te log(Te x) { return lut_log[x]; }
private:
Te lut_exp[(1 << N) * 2]; // Wrap to avoid indexing modulo 2^N-1
Te lut_log[1 << N];
@ -115,7 +117,8 @@ struct rs_engine
}
#if DEBUG_RS
fprintf(stderr, "RS generator:");
for ( int i=0; i<=16; ++i ) fprintf(stderr, " %02x", G[i]);
for (int i = 0; i <= 16; ++i)
fprintf(stderr, " %02x", G[i]);
fprintf(stderr, "\n");
#endif
}
@ -162,12 +165,13 @@ struct rs_engine
{
// TBD Avoid copying
u8 p[204];
memcpy(p, msg, 204); // was 188 but causing underflow (PVS https://www.viva64.com/en/w/v512/)
memcpy(p, msg, 188);
memset(p + 188, 0, 16);
// p = msg*X^16
#if DEBUG_RS
fprintf(stderr, "uncoded:");
for ( int i=0; i<204; ++i ) fprintf(stderr, " %d", p[i]);
for (int i = 0; i < 204; ++i)
fprintf(stderr, " %d", p[i]);
fprintf(stderr, "\n");
#endif
// Compute remainder modulo G
@ -183,7 +187,8 @@ struct rs_engine
}
#if DEBUG_RS
fprintf(stderr, "coded:");
for ( int i=0; i<204; ++i ) fprintf(stderr, " %d", p[i]);
for (int i = 0; i < 204; ++i)
fprintf(stderr, " %d", p[i]);
fprintf(stderr, "\n");
#endif
memcpy(msg + 188, p + 188, 16);
@ -193,14 +198,13 @@ struct rs_engine
// If pin[] is provided, errors will be fixed in the original
// message too and syndromes will be updated.
bool correct(u8 synd[16], u8 pout[188], u8 pin[204] = NULL, int *bits_corrected = NULL)
bool correct(u8 synd[16], u8 pout[188],
u8 pin[204] = NULL, int *bits_corrected = NULL)
{
// Berlekamp - Massey
// http://en.wikipedia.org/wiki/Berlekamp%E2%80%93Massey_algorithm#Code_sample
u8 C[16] =
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // Max degree is L
u8 B[16] =
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
u8 C[16] = {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Max degree is L
u8 B[16] = {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int L = 0;
int m = 1;
u8 b = 1;
@ -235,10 +239,12 @@ struct rs_engine
// C of degree L is now the error locator polynomial (Lambda)
#if DEBUG_RS
fprintf(stderr, "[L=%d C=", L);
for ( int i=0; i<16; ++i ) fprintf(stderr, " %d", C[i]);
for (int i = 0; i < 16; ++i)
fprintf(stderr, " %d", C[i]);
fprintf(stderr, "]\n");
fprintf(stderr, "[S=");
for ( int i=0; i<16; ++i ) fprintf(stderr, " %d", synd[i]);
for (int i = 0; i < 16; ++i)
fprintf(stderr, " %d", synd[i]);
fprintf(stderr, "]\n");
#endif
@ -255,7 +261,8 @@ struct rs_engine
omega[i + j] ^= gf.mul(synd[i], C[j]);
#if DEBUG_RS
fprintf(stderr, "omega=");
for ( int i=0; i<16; ++i ) fprintf(stderr, " %d", omega[i]);
for (int i = 0; i < 16; ++i)
fprintf(stderr, " %d", omega[i]);
fprintf(stderr, "\n");
#endif
@ -265,7 +272,8 @@ struct rs_engine
Cprime[i] = (i & 1) ? 0 : C[i + 1];
#if DEBUG_RS
fprintf(stderr, "Cprime=");
for ( int i=0; i<15; ++i ) fprintf(stderr, " %d", Cprime[i]);
for (int i = 0; i < 15; ++i)
fprintf(stderr, " %d", Cprime[i]);
fprintf(stderr, "\n");
#endif
@ -308,9 +316,8 @@ struct rs_engine
else
return false;
}
};
} // namespace
} // namespace leansdr
#endif // LEANSDR_RS_H

View File

@ -0,0 +1,98 @@
#include "sdr.h"
namespace leansdr
{
const char *cstln_base::names[] =
{
[BPSK] = "BPSK",
[QPSK] = "QPSK",
[PSK8] = "8PSK",
[APSK16] = "16APSK",
[APSK32] = "32APSK",
[APSK64E] = "64APSKe",
[QAM16] = "16QAM",
[QAM64] = "64QAM",
[QAM256] = "256QAM"
};
void softsymb_harden(llr_ss *ss)
{
for (int b = 0; b < 8; ++b)
ss->bits[b] = (ss->bits[b] < 0) ? -127 : 127;
}
void softsymb_harden(hard_ss *ss)
{
(void)ss; // NOP
}
void softsymb_harden(eucl_ss *ss)
{
for (int s = 0; s < ss->MAX_SYMBOLS; ++s)
ss->dists2[s] = (s == ss->nearest) ? 0 : 1;
}
uint8_t softsymb_to_dump(const llr_ss &ss, int bit)
{
return 128 - ss.bits[bit];
}
uint8_t softsymb_to_dump(const hard_ss &ss, int bit)
{
return ((ss >> bit) & 1) ? 255 : 0;
}
uint8_t softsymb_to_dump(const eucl_ss &ss, int bit)
{
(void)bit;
return ss.dists2[ss.nearest] >> 8;
}
void to_softsymb(const full_ss *fss, hard_ss *ss)
{
*ss = fss->nearest;
}
void to_softsymb(const full_ss *fss, eucl_ss *ss)
{
for (int s = 0; s < ss->MAX_SYMBOLS; ++s)
ss->dists2[s] = fss->dists2[s];
uint16_t best = 65535, best2 = 65535;
for (int s = 0; s < ss->MAX_SYMBOLS; ++s)
{
if (fss->dists2[s] < best)
{
best2 = best;
best = fss->dists2[s];
}
else if (fss->dists2[s] < best2)
{
best2 = fss->dists2[s];
}
}
ss->discr2 = best2 - best;
ss->nearest = fss->nearest;
}
void to_softsymb(const full_ss *fss, llr_ss *ss)
{
for (int b = 0; b < 8; ++b)
{
float v = (1.0f - fss->p[b]) / (fss->p[b] + 1e-6);
int r = logf(v) * 5; // TBD Optimal scaling vs saturation ?
if (r < -127)
r = -127;
if (r > 127)
r = 127;
ss->bits[b] = r;
}
}
} // leansdr

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,188 @@
#ifndef LEANSDR_SOFTWORD_H
#define LEANSDR_SOFTWORD_H
namespace leansdr
{
// This file implements options for SOFTBYTE in s2_frame_receiver
// (representation of bytes after deinterleaving).
// Also used as SOFTWORD in ldpc_engine
// (representation of packed SOFTBITs).
// Interface for ldpc_engine:
// SOFTBIT softword_get(const SOFTWORD &p, int b)
// SOFTBIT softwords_xor(const SOFTWORD p1[], const SOFTWORD p2[], int b)
// void softword_zero(SOFTWORD *p, int b)
// void softwords_set(SOFTWORD p[], int b)
// void softwords_flip(SOFTWORD p[], int b)
// HARD SOFTWORD / SOFTBYTE
typedef uint8_t hard_sb; // Bit 7 is transmitted first.
inline bool softword_get(const hard_sb &p, int b)
{
return p & (1 << (7 - b));
}
inline void softword_set(hard_sb *p, int b, bool v)
{
hard_sb mask = 1 << (7 - b);
*p = ((*p) & ~mask) | (v << (7 - b));
}
inline void softword_clear(hard_sb *p) { *p = 0; }
inline bool softword_weight(const bool &l)
{
return true;
}
inline void softbit_set(bool *p, bool v) { *p = v; }
inline bool softbit_harden(bool b) { return b; }
inline uint8_t softbyte_harden(const hard_sb &b) { return b; }
inline bool softbit_xor(bool x, bool y) { return x ^ y; }
inline void softbit_clear(bool *p) { *p = false; }
inline bool softwords_xor(const hard_sb p1[], const hard_sb p2[], int b)
{
return (p1[b / 8] ^ p2[b / 8]) & (1 << (7 - (b & 7)));
}
inline void softword_zero(hard_sb *p)
{
*p = 0;
}
inline void softwords_set(hard_sb p[], int b)
{
p[b / 8] |= 1 << (7 - (b & 7));
}
inline void softword_write(hard_sb &p, int b, bool v)
{
hard_sb mask = 1 << (7 - b);
p = (p & ~mask) | (hard_sb)v << (7 - b);
}
inline void softwords_write(hard_sb p[], int b, bool v)
{
softword_write(p[b / 8], b & 7, v);
}
inline void softwords_flip(hard_sb p[], int b)
{
p[b / 8] ^= 1 << (7 - (b & 7));
}
uint8_t *softbytes_harden(hard_sb p[], int nbytes, uint8_t storage[])
{
return p;
}
// LLR SOFTWORD
struct llr_sb
{
llr_t bits[8]; // bits[0] is transmitted first.
};
float prob(llr_t l)
{
return (127.0 + l) / 254;
}
llr_t llr(float p)
{
int r = -127 + 254 * p;
if (r < -127)
r = -127;
if (r > 127)
r = 127;
return r;
}
inline llr_t llr_xor(llr_t lx, llr_t ly)
{
float px = prob(lx), py = prob(ly);
return llr(px * (1 - py) + (1 - px) * py);
}
inline llr_t softword_get(const llr_sb &p, int b)
{
return p.bits[b];
}
// Special case to avoid computing b/8*8+b%7. Assumes llr_sb[] packed.
inline llr_t softwords_get(const llr_sb p[], int b)
{
return p[0].bits[b]; // Beyond bounds on purpose
}
inline void softword_set(llr_sb *p, int b, llr_t v)
{
p->bits[b] = v;
}
inline void softword_clear(llr_sb *p)
{
memset(p->bits, 0, sizeof(p->bits));
}
// inline llr_t softwords_get(const llr_sb p[], int b) {
// return softword_get(p[b/8], b&7);
// }
inline llr_t softword_weight(llr_t l) { return abs(l); }
inline void softbit_set(llr_t *p, bool v) { *p = v ? -127 : 127; }
inline bool softbit_harden(llr_t l) { return (l < 0); }
inline uint8_t softbyte_harden(const llr_sb &b)
{
// Without conditional jumps
uint8_t r = (((b.bits[0] & 128) >> 0) |
((b.bits[1] & 128) >> 1) |
((b.bits[2] & 128) >> 2) |
((b.bits[3] & 128) >> 3) |
((b.bits[4] & 128) >> 4) |
((b.bits[5] & 128) >> 5) |
((b.bits[6] & 128) >> 6) |
((b.bits[7] & 128) >> 7));
return r;
}
inline llr_t softbit_xor(llr_t x, llr_t y) { return llr_xor(x, y); }
inline void softbit_clear(llr_t *p) { *p = -127; }
inline llr_t softwords_xor(const llr_sb p1[], const llr_sb p2[], int b)
{
return llr_xor(p1[b / 8].bits[b & 7], p2[b / 8].bits[b & 7]);
}
inline void softword_zero(llr_sb *p)
{
memset(p, -127, sizeof(*p));
}
inline void softwords_set(llr_sb p[], int b)
{
p[b / 8].bits[b & 7] = 127;
}
inline void softword_write(llr_sb &p, int b, llr_t v)
{
p.bits[b] = v;
}
inline void softwords_write(llr_sb p[], int b, llr_t v)
{
softword_write(p[b / 8], b & 7, v);
}
inline void softwords_flip(llr_sb p[], int b)
{
llr_t *l = &p[b / 8].bits[b & 7];
*l = -*l;
}
uint8_t *softbytes_harden(llr_sb p[], int nbytes, uint8_t storage[])
{
for (uint8_t *q = storage; nbytes--; ++p, ++q)
*q = softbyte_harden(*p);
return storage;
}
// Generic wrappers
template <typename SOFTBIT, typename SOFTBYTE>
inline SOFTBIT softwords_get(const SOFTBYTE p[], int b)
{
return softword_get(p[b / 8], b & 7);
}
template <typename SOFTBIT, typename SOFTBYTE>
inline void softwords_set(SOFTBYTE p[], int b, SOFTBIT v)
{
softword_set(&p[b / 8], b & 7, v);
}
template <typename SOFTBIT, typename SOFTBYTE>
inline SOFTBIT softwords_weight(const SOFTBYTE p[], int b)
{
return softword_weight(softwords_get<SOFTBIT, SOFTBYTE>(p, b));
}
} // namespace leansdr
#endif // LEANSDR_SOFTWORD_H

View File

@ -1,3 +1,19 @@
// This file is part of LeanSDR Copyright (C) 2016-2018 <pabr@pabr.org>.
// See the toplevel README for more information.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef LEANSDR_VITERBI_H
#define LEANSDR_VITERBI_H
@ -50,8 +66,7 @@ struct trellis
{
if (NCS & (NCS - 1))
{
fprintf(stderr, "leansdr::trellis::init_convolutional: NCS must be a power of 2\n");
return;
fprintf(stderr, "NCS must be a power of 2\n");
}
// Derive number of polynomials from NCS.
int nG = log2i(NCS);
@ -76,14 +91,12 @@ struct trellis
typename state::branch *b = &states[shiftreg].branches[cs];
if (b->pred != NOSTATE)
{
fprintf(stderr, "leansdr::trellis::init_convolutional: Invalid convolutional code\n");
return;
fprintf(stderr, "Invalid convolutional code\n");
}
b->pred = s;
b->us = us;
}
}
}
void dump()
@ -102,19 +115,24 @@ struct trellis
fprintf(stderr, "\n");
}
}
};
// Interface that hides the templated internals.
template<typename TUS, typename TCS, typename TBM, typename TPM>
template <typename TUS,
typename TCS,
typename TBM,
typename TPM>
struct viterbi_dec_interface
{
virtual TUS update(TBM costs[], TPM *quality = NULL)=0;
virtual TUS update(TBM *costs, TPM *quality = NULL) = 0;
virtual TUS update(TCS s, TBM cost, TPM *quality = NULL) = 0;
virtual TUS update(int nm, TCS cs[], TBM costs[], TPM *quality = NULL)=0;
};
template<typename TS, int NSTATES, typename TUS, int NUS, typename TCS, int NCS, typename TBM, typename TPM, typename TP>
template <typename TS, int NSTATES,
typename TUS, int NUS,
typename TCS, int NCS,
typename TBM, typename TPM,
typename TP>
struct viterbi_dec : viterbi_dec_interface<TUS, TCS, TBM, TPM>
{
@ -129,8 +147,7 @@ struct viterbi_dec: viterbi_dec_interface<TUS, TCS, TBM, TPM>
state statebanks[2][NSTATES];
statebank *states, *newstates; // Alternate between banks
viterbi_dec(trellis<TS, NSTATES, TUS, NUS, NCS> *_trellis) :
trell(_trellis)
viterbi_dec(trellis<TS, NSTATES, TUS, NUS, NCS> *_trellis) : trell(_trellis)
{
states = &statebanks[0];
newstates = &statebanks[1];
@ -160,7 +177,8 @@ struct viterbi_dec: viterbi_dec_interface<TUS, TCS, TBM, TPM>
// Select best branch
for (int cs = 0; cs < NCS; ++cs)
{
typename trellis<TS, NSTATES, TUS, NUS, NCS>::state::branch *b = &trell->states[s].branches[cs];
typename trellis<TS, NSTATES, TUS, NUS, NCS>::state::branch *b =
&trell->states[s].branches[cs];
if (b->pred == trell->NOSTATE)
continue;
TPM m = (*states)[b->pred].cost + costs[cs];
@ -221,7 +239,8 @@ struct viterbi_dec: viterbi_dec_interface<TUS, TCS, TBM, TPM>
typename trellis<TS, NSTATES, TUS, NUS, NCS>::state::branch *best_b = NULL;
for (int im = 0; im < nm; ++im)
{
typename trellis<TS, NSTATES, TUS, NUS, NCS>::state::branch *b = &trell->states[s].branches[cs[im]];
typename trellis<TS, NSTATES, TUS, NUS, NCS>::state::branch *b =
&trell->states[s].branches[cs[im]];
if (b->pred == trell->NOSTATE)
continue;
TPM m = (*states)[b->pred].cost + costs[im];
@ -238,7 +257,8 @@ struct viterbi_dec: viterbi_dec_interface<TUS, TCS, TBM, TPM>
// This works because costs are negative.
for (int cs = 0; cs < NCS; ++cs)
{
typename trellis<TS, NSTATES, TUS, NUS, NCS>::state::branch *b = &trell->states[s].branches[cs];
typename trellis<TS, NSTATES, TUS, NUS, NCS>::state::branch *b =
&trell->states[s].branches[cs];
if (b->pred == trell->NOSTATE)
continue;
TPM m = (*states)[b->pred].cost;
@ -314,20 +334,11 @@ template<typename T, typename TUS, int NBITS, int DEPTH>
struct bitpath
{
T val;
bitpath() :
val(0)
{
}
void append(TUS us)
{
val = (val << NBITS) | us;
}
TUS read()
{
return (val >> (DEPTH - 1) * NBITS) & ((1 << NBITS) - 1);
}
bitpath() : val(0) {}
void append(TUS us) { val = (val << NBITS) | us; }
TUS read() { return (val >> (DEPTH - 1) * NBITS) & ((1 << NBITS) - 1); }
};
} // namespace
} // namespace leansdr
#endif // LEANSDR_VITERBI_H