mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-11-13 20:01:46 -05:00
640 lines
13 KiB
C++
640 lines
13 KiB
C++
// Copyright (C) 2018-2019, 2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
|
// Copyright (C) 2022 Jon Beniston, M7RCE <jon@beniston.com> //
|
|
|
|
// 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"
|
|
|
|
namespace leansdr
|
|
{
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Simple blocks
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
/* // Commented out, as not needed, as does compile on Windows
|
|
|
|
// [file_reader] reads raw data from a file descriptor into a [pipebuf].
|
|
// If the file descriptor is seekable, data can be looped.
|
|
|
|
template <typename T>
|
|
struct file_reader : runnable
|
|
{
|
|
bool loop;
|
|
|
|
file_reader(
|
|
scheduler *sch,
|
|
int _fdin,
|
|
pipebuf<T> &_out
|
|
) :
|
|
runnable(sch, _out.name),
|
|
loop(false),
|
|
filler(nullptr),
|
|
fdin(_fdin),
|
|
out(_out)
|
|
{
|
|
}
|
|
|
|
~file_reader()
|
|
{
|
|
delete[] filler;
|
|
}
|
|
|
|
void run()
|
|
{
|
|
size_t size = out.writable() * sizeof(T);
|
|
|
|
if (!size) {
|
|
return;
|
|
}
|
|
|
|
again:
|
|
ssize_t nr = read(fdin, out.wr(), size);
|
|
|
|
if (nr < 0 && errno == EWOULDBLOCK)
|
|
{
|
|
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, "%s looping\n", name);
|
|
}
|
|
|
|
off_t res = lseek(fdin, 0, SEEK_SET);
|
|
|
|
if (res == (off_t)-1) {
|
|
fatal("lseek");
|
|
}
|
|
|
|
goto again;
|
|
}
|
|
|
|
// Always stop at element boundary (may block)
|
|
size_t partial = nr % sizeof(T);
|
|
size_t remain = partial ? sizeof(T) - partial : 0;
|
|
|
|
while (remain)
|
|
{
|
|
if (sch->debug) {
|
|
fprintf(stderr, "+");
|
|
}
|
|
|
|
ssize_t nr2 = read(fdin, (char *)out.wr() + nr, remain);
|
|
|
|
if (nr2 <= 0) {
|
|
fatal("partial read");
|
|
}
|
|
|
|
nr += nr2;
|
|
remain -= nr2;
|
|
}
|
|
|
|
out.written(nr / sizeof(T));
|
|
}
|
|
|
|
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;
|
|
};
|
|
|
|
*/
|
|
|
|
// [file_writer] writes raw data from a [pipebuf] to a file descriptor.
|
|
|
|
template <typename T>
|
|
struct file_writer : runnable
|
|
{
|
|
file_writer(
|
|
scheduler *sch,
|
|
pipebuf<T> &_in,
|
|
int _fdout
|
|
) :
|
|
runnable(sch, _in.name),
|
|
in(_in),
|
|
fdout(_fdout)
|
|
{
|
|
}
|
|
|
|
void run()
|
|
{
|
|
int size = in.readable() * sizeof(T);
|
|
|
|
if (!size) {
|
|
return;
|
|
}
|
|
|
|
int nw = write(fdout, in.rd(), size);
|
|
|
|
if (!nw) {
|
|
fatal("pipe");
|
|
}
|
|
|
|
if (nw < 0) {
|
|
fatal("write");
|
|
}
|
|
|
|
if (nw % sizeof(T)) {
|
|
fatal("partial write");
|
|
}
|
|
|
|
in.read(nw / sizeof(T));
|
|
}
|
|
|
|
private:
|
|
pipereader<T> in;
|
|
int fdout;
|
|
};
|
|
|
|
// [file_printer] writes data from a [pipebuf] to a file descriptor,
|
|
// with printf-style formatting and optional scaling.
|
|
|
|
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)
|
|
{
|
|
}
|
|
|
|
void run()
|
|
{
|
|
int n = in.readable();
|
|
T *pin = in.rd(), *pend = pin + n;
|
|
|
|
for (; pin < pend; ++pin)
|
|
{
|
|
if (++phase >= decimation)
|
|
{
|
|
phase -= decimation;
|
|
char buf[256];
|
|
int len = snprintf(buf, sizeof(buf), format, (*pin) * scale);
|
|
|
|
if (len < 0) {
|
|
fatal("obsolete glibc");
|
|
}
|
|
|
|
int nw = write(fdout, buf, len);
|
|
|
|
if (nw != len) {
|
|
fatal("partial write");
|
|
}
|
|
}
|
|
}
|
|
|
|
in.read(n);
|
|
}
|
|
|
|
T scale;
|
|
int decimation;
|
|
|
|
private:
|
|
pipereader<T> in;
|
|
const char *format;
|
|
int fdout;
|
|
int phase;
|
|
};
|
|
|
|
// [file_carrayprinter] writes all data available from a [pipebuf]
|
|
// to a file descriptor on a single line.
|
|
// Special case for std::complex.
|
|
|
|
template <typename T>
|
|
struct file_carrayprinter : runnable
|
|
{
|
|
file_carrayprinter(
|
|
scheduler *sch,
|
|
const char *_head,
|
|
const char *_format,
|
|
const char *_sep,
|
|
const char *_tail,
|
|
pipebuf<std::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()
|
|
{
|
|
int n, nmin = fixed_size ? fixed_size : 1;
|
|
|
|
while ((n = in.readable()) >= nmin)
|
|
{
|
|
if (fixed_size) {
|
|
n = fixed_size;
|
|
}
|
|
|
|
if (fout)
|
|
{
|
|
fprintf(fout, head, n);
|
|
std::complex<T> *pin = in.rd();
|
|
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
if (i) {
|
|
fprintf(fout, "%s", sep);
|
|
}
|
|
|
|
fprintf(fout, format, pin[i].real() * scale, pin[i].imag() * scale);
|
|
}
|
|
|
|
fprintf(fout, "%s", tail);
|
|
}
|
|
|
|
fflush(fout);
|
|
in.read(n);
|
|
}
|
|
}
|
|
|
|
T scale;
|
|
int fixed_size; // Number of elements per batch, or 0.
|
|
|
|
private:
|
|
pipereader<std::complex<T>> in;
|
|
const char *head, *format, *sep, *tail;
|
|
FILE *fout;
|
|
};
|
|
|
|
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,
|
|
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("fdopen");
|
|
}
|
|
}
|
|
|
|
void run()
|
|
{
|
|
while (in.readable() >= 1)
|
|
{
|
|
fprintf(fout, head, n);
|
|
T(*pin)
|
|
[N] = in.rd();
|
|
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
if (i) {
|
|
fprintf(fout, "%s", sep);
|
|
}
|
|
|
|
fprintf(fout, format, (*pin)[i] * scale);
|
|
}
|
|
|
|
fprintf(fout, "%s", tail);
|
|
in.read(1);
|
|
}
|
|
|
|
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].
|
|
// [Tout] must be a numeric type.
|
|
|
|
template <typename Tin, typename Tout>
|
|
struct itemcounter : runnable
|
|
{
|
|
itemcounter(
|
|
scheduler *sch,
|
|
pipebuf<Tin> &_in,
|
|
pipebuf<Tout> &_out
|
|
) :
|
|
runnable(sch, "itemcounter"),
|
|
in(_in),
|
|
out(_out)
|
|
{
|
|
}
|
|
|
|
void run()
|
|
{
|
|
if (out.writable() < 1) {
|
|
return;
|
|
}
|
|
|
|
unsigned long count = in.readable();
|
|
|
|
if (!count) {
|
|
return;
|
|
}
|
|
|
|
out.write(count);
|
|
in.read(count);
|
|
}
|
|
|
|
private:
|
|
pipereader<Tin> in;
|
|
pipewriter<Tout> out;
|
|
};
|
|
|
|
// [decimator] forwards 1 in N sample.
|
|
|
|
template <typename T>
|
|
struct decimator : runnable
|
|
{
|
|
int d;
|
|
|
|
decimator(
|
|
scheduler *sch,
|
|
int _d,
|
|
pipebuf<T> &_in,
|
|
pipebuf<T> &_out
|
|
) :
|
|
runnable(sch, "decimator"),
|
|
d(_d),
|
|
in(_in),
|
|
out(_out)
|
|
{
|
|
}
|
|
|
|
void run()
|
|
{
|
|
long count = min(in.readable() / d, out.writable());
|
|
T *pin = in.rd(), *pend = pin + count * d, *pout = out.wr();
|
|
|
|
for (; pin < pend; pin += d, ++pout) {
|
|
*pout = *pin;
|
|
}
|
|
|
|
in.read(count * d);
|
|
out.written(count);
|
|
}
|
|
|
|
private:
|
|
pipereader<T> in;
|
|
pipewriter<T> out;
|
|
};
|
|
|
|
// [rate_estimator] accumulates counts of two quantities
|
|
// and periodically outputs their ratio.
|
|
|
|
template <typename T>
|
|
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)
|
|
{
|
|
}
|
|
|
|
void run()
|
|
{
|
|
if (rate.writable() < 1) {
|
|
return;
|
|
}
|
|
|
|
int count = min(num.readable(), den.readable());
|
|
int *pnum = num.rd(), *pden = den.rd();
|
|
|
|
for (int n = count; n--; ++pnum, ++pden)
|
|
{
|
|
acc_num += *pnum;
|
|
acc_den += *pden;
|
|
}
|
|
|
|
num.read(count);
|
|
den.read(count);
|
|
|
|
if (acc_den >= sample_size)
|
|
{
|
|
rate.write((float)acc_num / acc_den);
|
|
acc_num = acc_den = 0;
|
|
}
|
|
}
|
|
|
|
private:
|
|
pipereader<int> num, den;
|
|
pipewriter<float> rate;
|
|
T acc_num, acc_den;
|
|
};
|
|
|
|
// SERIALIZER
|
|
|
|
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)
|
|
{
|
|
(void)sch;
|
|
if (nin * sizeof(Tin) != nout * sizeof(Tout)) {
|
|
fail("serializer: incompatible sizes");
|
|
}
|
|
}
|
|
|
|
void run()
|
|
{
|
|
while (in.readable() >= nin && out.writable() >= nout)
|
|
{
|
|
memcpy(out.wr(), in.rd(), nout * sizeof(Tout));
|
|
in.read(nin);
|
|
out.written(nout);
|
|
}
|
|
}
|
|
|
|
private:
|
|
int nin, nout;
|
|
pipereader<Tin> in;
|
|
pipewriter<Tout> out;
|
|
}; // 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)
|
|
{
|
|
}
|
|
|
|
void run()
|
|
{
|
|
int n = min(out.writable(), (unsigned long)(count - pos));
|
|
memcpy(out.wr(), &data[pos], n * sizeof(T));
|
|
pos += n;
|
|
out.written(n);
|
|
}
|
|
|
|
private:
|
|
T *data;
|
|
int count;
|
|
pipewriter<T> out;
|
|
int pos;
|
|
}; // 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)
|
|
{
|
|
}
|
|
|
|
void run()
|
|
{
|
|
int n = min(in.readable(), (unsigned long)(count - pos));
|
|
memcpy(&data[pos], in.rd(), n * sizeof(T));
|
|
in.read(n);
|
|
pos += n;
|
|
}
|
|
|
|
private:
|
|
pipereader<T> in;
|
|
T *data;
|
|
int count;
|
|
int pos;
|
|
}; // buffer_writer
|
|
|
|
} // namespace leansdr
|
|
|
|
#endif // LEANSDR_GENERIC_H
|