/*---------------------------------------------------------------------------*\
FILE........: ch.c
AUTHOR......: David Rowe
DATE CREATED: May 2015
Channel simulation program for testing command line versions of modems.
\*---------------------------------------------------------------------------*/
/*
Copyright (C) 2015-2022 David Rowe
All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License version 2.1, as
published by the Free Software Foundation. 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 Lesser General Public License
along with this program; if not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include "freedv_api.h"
#include "codec2_cohpsk.h"
#include "comp_prim.h"
#include "ht_coeff.h"
#include "ssbfilt_coeff.h"
#include "debug_alloc.h"
#define BUF_N 160
#define MPG_DELAY_MS 0.5
#define MPP_DELAY_MS 2.0
#define MPD_DELAY_MS 4.0
/* see instructions below for how to generate these files */
#define DEFAULT_FADING_DIR "unittest"
#define MPG_FADING_FILE_NAME "slow_fading_samples.float"
#define MPP_FADING_FILE_NAME "fast_fading_samples.float"
#define MPD_FADING_FILE_NAME "faster_fading_samples.float"
// Gaussian from uniform:
float gaussian(void) {
double x = (double)rand() / RAND_MAX;
double y = (double)rand() / RAND_MAX;
double z = sqrt(-2 * log(x)) * cos(2 * M_PI * y);
return sqrt(1./2.) * z;
}
// complex noise sample
COMP noise(void) {
COMP n = {gaussian(),gaussian()};
return n;
}
int main(int argc, char *argv[])
{
FILE *fin, *ffading, *fout;
char *fading_dir;
float NodB, foff_hz;
int fading_en, nhfdelay;
short buf[BUF_N];
float htbuf[HT_N+BUF_N];
COMP ch_in[BUF_N];
COMP ch_fdm[BUF_N];
COMP ssbfiltbuf[SSBFILT_N+BUF_N];
COMP ssbfiltout[BUF_N];
COMP phase_ch;
float No, variance;
COMP scaled_noise;
float hf_gain;
COMP *ch_fdm_delay = NULL, aspread, aspread_2ms, delayed, direct;
float tx_pwr, tx_pwr_fade, noise_pwr, user_multipath_delay;
int frames, i, j, k, Fs, ret, nclipped, noutclipped, ssbfilt_en, complex_out, ctest;
float sam, peak, clip, papr, CNo, snr3k, gain;
if (argc < 3) {
helpmsg:
fprintf(stderr, "Command line channel simulation tool.\n"
"\n"
"usage: %s InputRealModemRawFile OutputRealModemRawFile [Options]\n"
"\n"
" real int16 input -> Gain -> Hilbert Transform -> clipper -> freq shift ->\n"
" Multipath -> AWGN noise -> SSB filter -> real int16 output\n"
"\n"
"[--clip int16] Hilbert clipper (clip complex signal magnitude, default 32767)\n"
"[--complexout] Optional int16 IQ complex output (default real int16)\n"
"[--ctest] Check PAPR is around 0dB, used to support ctests\n"
"[--freq FoffHz] Frequency offset (default 0Hz)\n"
"[--fading_dir Path] path to multipath fading files (default 'unittest')\n"
"[--Fs SampleRateHz] Sample rate of simulation (default 8000 Hz)\n"
"[--gain G] Linear gain (default 1.0)\n"
"[--mpg] Multipath good 0.1Hz Doppler, 0.5ms delay\n"
"[--mpp] Multipath poor 1.0Hz Doppler, 2.0ms delay\n"
"[--mpd] Multipath disturbed 2.0Hz Doppler, 4.0ms delay\n"
"[--ssbfilt 0|1] SSB bandwidth filter (default 1 on)\n"
"[--mulipath_delay ms] Optionally adjust multipath delay\n"
"[--No dBHz] AWGN Noise density dB/Hz (default -100)"
"\n"
, argv[0]);
exit(1);
}
if (strcmp(argv[1], "-") == 0) fin = stdin;
else if ( (fin = fopen(argv[1],"rb")) == NULL ) {
fprintf(stderr, "ch: Error opening input modem raw file: %s: %s.\n",
argv[1], strerror(errno));
exit(1);
}
if (strcmp(argv[2], "-") == 0) fout = stdout;
else if ( (fout = fopen(argv[2],"wb")) == NULL ) {
fprintf(stderr, "ch: Error opening output modem raw file: %s: %s.\n",
argv[2], strerror(errno));
exit(1);
}
NodB = -100;
Fs = 8000; foff_hz = 0.0; fading_en = 0; ctest = 0;
clip =32767; gain = 1.0;
ssbfilt_en = 1; complex_out = 0;
fading_dir = strdup(DEFAULT_FADING_DIR); user_multipath_delay = -1.0;
int o = 0;
int opt_idx = 0;
while( o != -1 ){
static struct option long_opts[] = {
{"complexout", no_argument, 0, 'o'},
{"ctest", no_argument, 0, 't'},
{"clip", required_argument, 0, 'c'},
{"fading_dir", required_argument, 0, 'u'},
{"freq", required_argument, 0, 'f'},
{"Fs", required_argument, 0, 'r'},
{"gain", required_argument, 0, 'g'},
{"ssbfilt", required_argument, 0, 's'},
{"help", no_argument, 0, 'h'},
{"mpg", no_argument, 0, 'i'},
{"mpp", no_argument, 0, 'p'},
{"mpd", no_argument, 0, 'd'},
{"multipath_delay", required_argument, 0, 'm'},
{"No", required_argument, 0, 'n'},
{0, 0, 0, 0}
};
o = getopt_long(argc,argv,"c:df:g:im:n:opr:s:tu:h",long_opts,&opt_idx);
switch(o) {
case 'c':
clip = atof(optarg);
break;
case 'd':
fading_en = 3;
break;
case 'f':
foff_hz = atof(optarg);
break;
case 'g':
gain = atof(optarg);
break;
case 'i':
fading_en = 1;
break;
case 'm':
user_multipath_delay = atof(optarg);
break;
case 'n':
NodB = atof(optarg);
break;
case 'o':
complex_out = 1;
break;
case 'p':
fading_en = 2;
break;
case 'r':
Fs = atoi(optarg);
break;
case 's':
ssbfilt_en = atoi(optarg);
break;
case 't':
ctest = 1;
break;
case 'u':
fading_dir = strdup(optarg);
break;
case 'h':
case '?':
goto helpmsg;
break;
}
}
phase_ch.real = 1.0; phase_ch.imag = 0.0;
/* N = var = NoFs */
// arbitrary noise scaling, to maintain backwards compatibility with many tests. TODO make the No
// units more sensible, and fix all the tests that depend on this scaling
No = pow(10.0, NodB/10.0)*1000*1000;
variance = Fs*No;
tx_pwr = tx_pwr_fade = noise_pwr = 0.0;
noutclipped = 0; nclipped = 0;
peak = 0.0;
/* init HF fading model */
ffading = NULL;
nhfdelay = 0;
if (fading_en) {
char fname[256];
if (fading_en == 1) {
sprintf(fname, "%s/%s", fading_dir, MPG_FADING_FILE_NAME);
ffading = fopen(fname, "rb");
if (ffading == NULL) {
cant_load_fading_file:
fprintf(stderr, "-----------------------------------------------------\n");
fprintf(stderr, "ch ERROR: Can't find fading file: %s\n", fname);
fprintf(stderr, "\nAdjust path --fading_dir or use GNU Octave to generate:\n\n");
gen_fading_file:
fprintf(stderr, "$ octave --no-gui\n");
fprintf(stderr, "octave:24> pkg load signal\n");
fprintf(stderr, "octave:24> time_secs=60\n");
fprintf(stderr, "octave:25> ch_fading(\"faster_fading_samples.float\", 8000, 2.0, 8000*time_secs)\n");
fprintf(stderr, "octave:26> ch_fading(\"fast_fading_samples.float\", 8000, 1.0, 8000*time_secs)\n");
fprintf(stderr, "octave:27> ch_fading(\"slow_fading_samples.float\", 8000, 0.1, 8000*time_secs)\n");
fprintf(stderr, "-----------------------------------------------------\n");
exit(1);
}
nhfdelay = floor(MPG_DELAY_MS*Fs/1000);
}
if (fading_en == 2) {
sprintf(fname, "%s/%s", fading_dir, MPP_FADING_FILE_NAME);
ffading = fopen(fname, "rb");
if (ffading == NULL) goto cant_load_fading_file;
nhfdelay = floor(MPP_DELAY_MS*Fs/1000);
}
if (fading_en == 3) {
sprintf(fname, "%s/%s", fading_dir, MPD_FADING_FILE_NAME);
ffading = fopen(fname, "rb");
if (ffading == NULL) goto cant_load_fading_file;
nhfdelay = floor(MPD_DELAY_MS*Fs/1000);
}
ch_fdm_delay = (COMP*)MALLOC((nhfdelay+COHPSK_NOM_SAMPLES_PER_FRAME)*sizeof(COMP));
assert(ch_fdm_delay != NULL);
for(i=0; i= 0.0) nhfdelay = floor(user_multipath_delay*Fs/1000);
/* first values in file are HF gains */
for (i=0; i<4; i++)
ret = fread(&hf_gain, sizeof(float), 1, ffading);
//fprintf(stderr, "hf_gain: %f\n", hf_gain);
}
assert(HT_N == sizeof(ht_coeff)/sizeof(COMP));
for(i=0; i clip) {
mag = clip;
nclipped++;
}
tx_pwr += mag*mag;
/* we get a bit of overshoot in peak measurements if HT filter hasn't been primed */
if (frames*BUF_N > HT_N)
if (mag > peak) {
peak = mag;
//fprintf(stderr, "%d %f\n",frames, mag);
}
ch_in[i].real = mag*cos(angle);
ch_in[i].imag = mag*sin(angle);
}
/* --------------------------------------------------------*\
Channel
\*---------------------------------------------------------*/
fdmdv_freq_shift_coh(ch_fdm, ch_in, foff_hz, Fs, &phase_ch, BUF_N);
/* optional HF fading -------------------------------------*/
if (fading_en) {
/* update delayed signal buffer */
for(i=0; i 32767.0) { noutclipped++; sam = 32767.0; }
if (sam < -32767.0) { noutclipped++; sam = -32767.0; }
*pout++ = sam;
if (complex_out) {
sam = ssbfiltout[i].imag;
if (sam > 32767.0) { noutclipped++; sam = 32767.0; }
if (sam < -32767.0) { noutclipped++; sam = -32767.0; }
*pout++ = sam;
}
}
fwrite(bufout, sizeof(short), nout, fout);
/* if this is in a pipeline, we probably don't want the usual
buffering to occur */
if (fout == stdout) fflush(stdout);
}
fclose(fin);
fclose(fout);
int nsamples = frames*BUF_N;
papr = 10*log10(peak*peak/(tx_pwr/nsamples));
CNo = 10*log10(tx_pwr/(noise_pwr/(Fs)));
snr3k = CNo - 10*log10(3000);
float outclipped_percent = noutclipped*100.0/nsamples;
fprintf(stderr, "ch: SNR3k(dB): %8.2f C/No....: %8.2f\n", snr3k, CNo);
fprintf(stderr, "ch: peak.....: %8.2f RMS.....: %8.2f CPAPR.....: %5.2f \n", peak, sqrt(tx_pwr/nsamples), papr);
fprintf(stderr, "ch: Nsamples.: %8d clipped.: %8.2f%% OutClipped: %5.2f%%\n",
nsamples, nclipped*100.0/nsamples, outclipped_percent);
if (outclipped_percent > 0.1) fprintf(stderr, "ch: WARNING output clipping\n");
if (ffading != NULL) fclose(ffading);
if (ch_fdm_delay != NULL) FREE(ch_fdm_delay);
if (ctest) {
/* special ctest mode: check CPAPR is around 0dB */
if (fabs(papr) < 0.7) return 0; else return 1;
}
else return 0;
}