kopia lustrzana https://github.com/pabr/leansdr
271 wiersze
8.3 KiB
C++
271 wiersze
8.3 KiB
C++
// 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/>.
|
|
|
|
|
|
// Simple channel simulator, for benchmarking.
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "leansdr/framework.h"
|
|
#include "leansdr/generic.h"
|
|
#include "leansdr/dsp.h"
|
|
|
|
using namespace leansdr;
|
|
|
|
typedef float f32;
|
|
typedef complex<f32> cf32;
|
|
typedef unsigned char u8;
|
|
typedef complex<u8> cu8;
|
|
|
|
template<typename T>
|
|
struct drifter : runnable {
|
|
drifter(scheduler *sch,
|
|
pipebuf< complex<T> > &_in, pipebuf< complex<T> > &_out)
|
|
: runnable(sch, "drifter"),
|
|
in(_in), out(_out),
|
|
t(0) {
|
|
memset(drifts, 0, sizeof(drifts));
|
|
for ( int i=0; i<65536; ++i ) {
|
|
float a = 2*M_PI * i / 65536;
|
|
lut_trig[i].re = cosf(a);
|
|
lut_trig[i].im = sinf(a);
|
|
}
|
|
}
|
|
|
|
static const int NCOMPONENTS = 3;
|
|
|
|
struct component {
|
|
float amp; // Amplitude of fluctuation (Hz)
|
|
float freq; // Rate of fluctuation (Hz)
|
|
signed long a; // Phase at runtime (2pi/2^32)
|
|
} drifts[NCOMPONENTS];
|
|
|
|
void run() {
|
|
unsigned long count = min(in.readable(), out.writable());
|
|
complex<T> *pin = in.rd(), *pend = pin+count;
|
|
complex<T> *pout = out.wr();
|
|
signed short phase = 0;
|
|
for ( ; pin<pend; ++pin,++pout,t+=1 ) {
|
|
float f = 0;
|
|
for ( int i=0; i<NCOMPONENTS; ++i ) {
|
|
complex<float> *r = &lut_trig[(unsigned short)(drifts[i].a>>16)];
|
|
f += drifts[i].amp * r->im;
|
|
drifts[i].a += drifts[i].freq * 4294967296.0;
|
|
}
|
|
phase += f * 65536;
|
|
complex<float> *r = &lut_trig[(unsigned short)phase];
|
|
pout->re = pin->re*r->re - pin->im*r->im;
|
|
pout->im = pin->re*r->im + pin->im*r->re;
|
|
}
|
|
in.read(count);
|
|
out.written(count);
|
|
}
|
|
|
|
private:
|
|
complex<float> lut_trig[65536];
|
|
pipereader< complex<T> > in;
|
|
pipewriter< complex<T> > out;
|
|
unsigned long t;
|
|
};
|
|
|
|
struct config {
|
|
float loop_input;
|
|
enum { IO_F32, IO_U8 } input_format, output_format;
|
|
float scale; // Input gain factor
|
|
float awgn; // White gaussian noise standard deviation
|
|
bool deterministic; // Pseudorandom noise
|
|
float Fs; // Sampling rate
|
|
float Flo; // Local oscillator freq
|
|
float ppm; // Local oscillator accuracy
|
|
float drift_period, drift_rate;
|
|
float drift2_amp, drift2_freq;
|
|
// struct drifter::component drifts[5];
|
|
|
|
config() :
|
|
loop_input(false),
|
|
input_format(IO_F32),
|
|
output_format(IO_F32),
|
|
scale(1),
|
|
awgn(0),
|
|
deterministic(false),
|
|
Fs(0),
|
|
Flo(0),
|
|
ppm(-1), drift_period(0), drift_rate(0),
|
|
drift2_amp(0), drift2_freq(0)
|
|
{
|
|
}
|
|
};
|
|
|
|
typedef complex<float> cf32;
|
|
|
|
int run(config &cfg) {
|
|
scheduler sch;
|
|
unsigned long BUF_BASEBAND = 4096;
|
|
|
|
pipebuf<cf32> *pipe = NULL;
|
|
|
|
switch ( cfg.input_format) {
|
|
case config::config::IO_F32: {
|
|
pipebuf<cf32> *p_stdin =
|
|
new pipebuf<cf32>(&sch, "stdin", BUF_BASEBAND);
|
|
file_reader<cf32> *r_stdin = new file_reader<cf32>(&sch, 0, *p_stdin);
|
|
r_stdin->loop = cfg.loop_input;
|
|
pipe = p_stdin;
|
|
break;
|
|
}
|
|
case config::IO_U8: {
|
|
pipebuf<cu8> *p_stdin = new pipebuf<cu8>(&sch, "stdin", BUF_BASEBAND);
|
|
file_reader<cu8> *r_stdin = new file_reader<cu8>(&sch, 0, *p_stdin);
|
|
r_stdin->loop = cfg.loop_input;
|
|
pipebuf<cf32> *p_stdinf =
|
|
new pipebuf<cf32>(&sch, "stdinf", BUF_BASEBAND);
|
|
new cconverter<u8,128, f32,0, 1,1>(&sch, *p_stdin, *p_stdinf);
|
|
pipe = p_stdinf;
|
|
break;
|
|
}
|
|
}
|
|
|
|
pipebuf<cf32> p_scaled(&sch, "scaled", BUF_BASEBAND);
|
|
scaler<float,cf32,cf32> r_scale(&sch, cfg.scale, *pipe, p_scaled);
|
|
pipe = &p_scaled;
|
|
|
|
if ( ! cfg.deterministic )
|
|
srand48(getpid());
|
|
pipebuf<cf32> p_noise(&sch, "noise", BUF_BASEBAND);
|
|
wgn_c<f32> r_noise(&sch, p_noise);
|
|
r_noise.stddev = cfg.awgn;
|
|
pipebuf<cf32> p_noisy(&sch, "noisy", BUF_BASEBAND);
|
|
adder<cf32> r_addnoise(&sch, *pipe, p_noise, p_noisy);
|
|
pipe = &p_noisy;
|
|
|
|
pipebuf<cf32> p_drift(&sch, "drift", BUF_BASEBAND);
|
|
drifter<float> r_drift(&sch, *pipe, p_drift);
|
|
float maxoffs = cfg.Flo * cfg.ppm * 1e-6;
|
|
r_drift.drifts[0].amp = maxoffs / cfg.Fs;
|
|
if ( cfg.drift_period && cfg.drift_rate )
|
|
fail("Specify only one of --drift-rate and --drift-period");
|
|
if ( cfg.drift_period )
|
|
r_drift.drifts[0].freq = (1.0/cfg.drift_period) / cfg.Fs;
|
|
if ( cfg.drift_rate ) {
|
|
if ( ! cfg.ppm ) fail("Need --ppm with --drift-rate");
|
|
r_drift.drifts[0].freq = (cfg.drift_rate/(2*M_PI*cfg.ppm)) / cfg.Fs;
|
|
}
|
|
if ( cfg.drift2_amp && cfg.drift2_freq ) {
|
|
r_drift.drifts[1].amp = cfg.drift2_amp / cfg.Fs;
|
|
r_drift.drifts[1].freq = cfg.drift2_freq / cfg.Fs;
|
|
}
|
|
pipe = &p_drift;
|
|
|
|
switch ( cfg.output_format ) {
|
|
case config::IO_U8: {
|
|
pipebuf<cu8> *p_stdout = new pipebuf<cu8>(&sch, "stdou", BUF_BASEBAND);
|
|
new cconverter<f32,0, u8,128, 1,1>(&sch, *pipe, *p_stdout);
|
|
new file_writer<cu8>(&sch, *p_stdout, 1);
|
|
break;
|
|
}
|
|
case config::IO_F32: {
|
|
new file_writer<cf32>(&sch, *pipe, 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
sch.run();
|
|
|
|
return 0;
|
|
}
|
|
|
|
void usage(const char *name, FILE *f, int c) {
|
|
fprintf(f, "Usage: %s [options] < IQ.in > IQ.out\n", name);
|
|
fprintf(f, "Simulate an imperfect communication channel.\n");
|
|
fprintf(f,
|
|
"\nInput options:\n"
|
|
" --iu8 Interpret stdin as complex unsigned char\n"
|
|
" --if32 Interpret stdin as complex float\n"
|
|
" -f Hz Specify sample rate\n"
|
|
" --loop Repeat (stdin must be a file)\n"
|
|
);
|
|
fprintf(f,
|
|
"\nGain options:\n"
|
|
" --scale FACTOR Multiply by constant\n"
|
|
);
|
|
fprintf(f,
|
|
"\nDrift options:\n"
|
|
" --lo HZ Specify nominal LO frequency\n"
|
|
" --ppm PPM Specify LO accuracy\n"
|
|
" --drift-period S Drift +-ppm every S seconds\n"
|
|
" --drift-rate R Drift with maximum rate R (Hz/s)\n"
|
|
" --drift2-amp HZ Add secondary drift (range in Hz)\n"
|
|
" --drift2-freq HZ Add secondary drift (rate in Hz)\n"
|
|
);
|
|
fprintf(f,
|
|
"\nNoise options:\n"
|
|
" --awgn STDDEV Add white gaussian noise (dB)\n"
|
|
);
|
|
fprintf(f,
|
|
"\nOutput options:\n"
|
|
" --ou8 Output as complex unsigned char\n"
|
|
" --of32 Output as complex float\n"
|
|
);
|
|
exit(c);
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
config cfg;
|
|
|
|
for ( int i=1; i<argc; ++i ) {
|
|
if ( ! strcmp(argv[i], "-h") )
|
|
usage(argv[0], stdout, 0);
|
|
else if ( ! strcmp(argv[i], "--iu8") )
|
|
cfg.input_format = config::IO_U8;
|
|
else if ( ! strcmp(argv[i], "--if32") )
|
|
cfg.input_format = config::IO_F32;
|
|
else if ( ! strcmp(argv[i], "--loop") )
|
|
cfg.loop_input = true;
|
|
else if ( ! strcmp(argv[i], "--ou8") )
|
|
cfg.output_format = config::IO_U8;
|
|
else if ( ! strcmp(argv[i], "--of32") )
|
|
cfg.output_format = config::IO_F32;
|
|
else if ( ! strcmp(argv[i], "-f") && i+1<argc )
|
|
cfg.Fs = atof(argv[++i]);
|
|
// Scale
|
|
else if ( ! strcmp(argv[i], "--scale") && i+1<argc )
|
|
cfg.scale = atof(argv[++i]);
|
|
// Noise
|
|
else if ( ! strcmp(argv[i], "--awgn") && i+1<argc )
|
|
cfg.awgn = expf(logf(10)*atof(argv[++i])/20);
|
|
else if ( ! strcmp(argv[i], "--deterministic") )
|
|
cfg.deterministic = true;
|
|
// Drift
|
|
else if ( ! strcmp(argv[i], "--lo") && i+1<argc )
|
|
cfg.Flo = atof(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--ppm") && i+1<argc )
|
|
cfg.ppm = atof(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--drift-period") && i+1<argc )
|
|
cfg.drift_period = atof(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--drift-rate") && i+1<argc )
|
|
cfg.drift_rate = atof(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--drift2-amp") && i+1<argc )
|
|
cfg.drift2_amp = atof(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--drift2-freq") && i+1<argc )
|
|
cfg.drift2_freq = atof(argv[++i]);
|
|
else
|
|
usage(argv[0], stderr, 1);
|
|
}
|
|
|
|
return run(cfg);
|
|
}
|