// ---------------------------------------------------------------------------- // bmorse.c -- bayesian morse code decoder // // Copyright (C) 2012-2014 Mauri Niininen, AG1LE // // This file is part of Bayesian Morse code decoder // Parts of this is adapted from FLDIGI cw.cxx // Copyright (C) 2006-2010 Dave Freese, W1HKJ // Parts of this is adapted from sndfile-spectrogram // Copyright (C) 2007-2009 Erik de Castro Lopo // // bmorse 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. // // bmorse 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 bmorse. If not, see . // --------------------------------------------------------------------------- #include #include #include #include #include #include #include #include "complex.h" #include "window.h" #include "bmorse.h" #include "fftfilt.h" #define ARRAY_LEN(x) ((int) (sizeof (x) / sizeof (x [0]))) #define MAX(x,y) ((x) > (y) ? (x) : (y)) #define MIN(x,y) ((x) < (y) ? (x) : (y)) #define MIN_WIDTH 640 #define MIN_HEIGHT 480 #define MAX_WIDTH 65536 #define MAX_HEIGHT 4096 #define TWOPI 2.0*M_PI #define DEC_RATIO 20 /* { int print_variables ; // FALSE int print_symbols; // FALSE int print_speed; // FALSE int process_textfile; // FALSE int print_text; // FALSE int print_xplot; // FALSE int width; // 8192 int speclen ; // 32 int bfv; // 0 double frequency; // 600 double sample_duration; // 5 double sample_rate; // 0 double delta; // 10.0 double amplify; // 0.0 int fft; // 0 int agc; // 0 int speed; // 20 int dec_ratio; // 20 (4000 Hz/ 20 => 200 Hz ) decimation ratio samplerate / bayes decoder rate */ PARAMS params = { FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, 8192, 32, 0, 600, 5, 4000, 10.0, 0.0, 0, 0,20,20}; fftfilt *FFT_filter; double filter(double a, int len) { static double in[2000]; static double out; static int i, pint; static int empty = 1; if ((len == 0) || (len ==1)) { return a; } if (empty) { empty = 0; for (i = 0; i < len; i++) { in[i] = a; } out = a * len; pint = 0; return a; } out = out - in[pint] + a; in[pint] = a; if (++pint >= len) pint = 0; return out / len; } double decayavg(double average, double input, double weight) { if (weight <= 1.0) return input; return input * (1.0 / weight) + average * (1.0 - (1.0 / weight)); } sf_count_t sfx_mix_mono_read_double (SNDFILE * file, double * data, sf_count_t datalen) { SF_INFO info ; #if HAVE_SF_GET_INFO /* ** The function sf_get_info was in a number of 1.0.18 pre-releases but was removed ** before 1.0.18 final and replaced with the SFC_GET_CURRENT_SF_INFO command. */ sf_get_info (file, &info) ; #else sf_command (file, SFC_GET_CURRENT_SF_INFO, &info, sizeof (info)) ; #endif if (info.channels == 1) return sf_read_double (file, data, datalen) ; static double multi_data [2048] ; int k, ch, frames_read ; sf_count_t dataout = 0 ; while (dataout < datalen) { int this_read ; this_read = MIN (ARRAY_LEN (multi_data) / info.channels, datalen) ; frames_read = sf_readf_double (file, multi_data, this_read) ; if (frames_read == 0) break ; for (k = 0 ; k < frames_read ; k++) { double mix = 0.0 ; for (ch = 0 ; ch < info.channels ; ch++) mix += multi_data [k * info.channels + ch] ; data [dataout + k] = mix / info.channels ; } ; dataout += this_read ; } ; return dataout ; } /* sfx_mix_mono_read_double */ static void read_mono_audio (SNDFILE * file, sf_count_t filelen, double * data, int datalen, int indx, int total) { sf_count_t start; memset (data, 0, datalen * sizeof (data [0])) ; start = (indx * filelen) / total - datalen / 2 ; if (start >= 0) sf_seek (file, start, SEEK_SET) ; else { start = -start ; sf_seek (file, 0, SEEK_SET) ; data += start ; datalen -= start ; } ; sfx_mix_mono_read_double (file, data, datalen) ; return ; } /* read_mono_audio */ static double calc_magnitude (const double * freq, int freqlen, double * magnitude) { int k ; double max = 0.0 ; for (k = 1 ; k < freqlen / 2 ; k++) { magnitude[k] = sqrt (freq [k] * freq [k] + freq [freqlen - k - 1] * freq [freqlen - k - 1]) ; max = MAX (max, magnitude [k]) ; } ; magnitude [0] = 0.0 ; return max ; } /* calc_magnitude */ #define PEAKS_SIZE 2000 struct PEAKS { double mx[PEAKS_SIZE]; double mn[PEAKS_SIZE]; int mxpos[PEAKS_SIZE]; int mnpos[PEAKS_SIZE]; int mxcount; int mncount; double delta; }; // peak_detect() detects peaks in a vector ******************************************************************************* // Finds the local maxima and minima ("peaks") in the vector V. // A point is considered a maximum peak if it has the maximal // value, and was preceded (to the left) by a value lower by DELTA. void peak_detect(double *v, int length, struct PEAKS *p) { int i, lookformax; double value, mn,mx; int mnpos,mxpos; p->mxcount = 0; p->mncount = 0; if (p->delta <= 0) { printf ("%s : line %d :Input argument DELTA must be positive.\n", __FILE__, __LINE__) ; exit (1) ; } mn = 1.0/0.0; mx = -1.0/0.0; mnpos = -10000; mxpos = 10000; lookformax = 1; for (i=0; i< length; i++) { value = v[i]; if (value > mx) {mx = value; mxpos = i;} if (value < mn) {mn = value; mnpos = i;} if (lookformax){ if (value < mx-p->delta) { p->mx[p->mxcount] = mx; p->mxpos[p->mxcount] = mxpos; p->mxcount += 1; mn = value; mnpos = i; lookformax = 0; }; } else { if (value > mn+p->delta) { p->mn[p->mncount] = mn; p->mnpos[p->mncount] = mnpos; p->mncount += 1; mx = value; mxpos = i; lookformax = 1; }; } } } static void apply_window (double * data, int datalen) { static double window [10 * MAX_HEIGHT] ; static int window_len = 0 ; int k ; if (window_len != datalen) { window_len = datalen ; if (datalen > ARRAY_LEN (window)) { printf ("%s : datalen > MAX_HEIGHT\n", __func__) ; exit (1) ; } ; calc_kaiser_window (window, datalen, 20.0) ; // calc_nuttall_window (window, datalen); } ; for (k = 0 ; k < datalen ; k++) data [k] *= window [k] ; return ; } /* apply_window */ static void interp_spec (float * mag, int maglen, const double *spec, int speclen) { int k, lastspec = 0 ; mag [0] = spec [0] ; for (k = 1 ; k < maglen ; k++) { double sum = 0.0 ; int count = 0 ; do { sum += spec [lastspec] ; lastspec ++ ; count ++ ; } while (lastspec <= ceil ((k * speclen) / maglen)) ; mag [k] = sum / count ; } ; return ; } /* interp_spec */ void process_data(double x) { static integer sample_counter = 0; static real rn = .1f; static integer retstat, n1, n2, imax, xhat, elmhat; static real pmax, zout, spdhat, px; static int init = 1,pinit = 1; static double agc_peak = 0.0; static morse* mp; if (init) { mp = new morse(); init = 0; } if (params.amplify != 0.0) x = params.amplify * x; if (params.agc) { if (x > agc_peak) agc_peak = decayavg(agc_peak, x, 10); else agc_peak = decayavg(agc_peak, x, 900); if (agc_peak != 0.0){ x /= agc_peak; x = clamp(x, 0.0, 1.0); } else x = 0.0; } if (params.print_xplot) printf("\n%f",x); if (params.print_variables && pinit) { //print variable labels first (only once) iu printf("\nretstat\timax\telmhat\txhat\tx\tpx\tpmax\tspdhat\trn\tzout\tp1\tp2\tp3\tp4\tp5\tp6\n"); pinit = 0; } mp->noise_(x, &rn, &zout); zout = clamp(zout, 0.0, 1.0); // if (zout > 1.0) zout = 1.0; // if (zout < 0.0) zout = 0.0; retstat = mp->proces_(zout, rn, &xhat, &px, &elmhat, &spdhat, &imax, &pmax, params.speed); if (params.print_variables) printf("\n%d\t%d\t%d\t%d\t%f\t%f\t%f\t%f\t%f\t%f",(int)retstat,(int)imax,(int)elmhat,(int)xhat,x,px,pmax,spdhat,rn,zout); if (params.print_speed) printf("\n%f", spdhat); } int rx_FFTprocess(const double *buf, int len) { complex z, *zp; int n,i; static int smpl_ctr = 0; static double FFTvalue,FFTphase =0.0; while (len-- > 0) { // convert CW signal to baseband z = complex ( *buf * cos(FFTphase), *buf * sin(FFTphase) ); FFTphase += TWOPI * params.frequency / params.sample_rate; if (FFTphase > M_PI) FFTphase -= TWOPI; else if (FFTphase < M_PI) FFTphase += TWOPI; buf++; // run low pass filter n = FFT_filter->run(z, &zp); // n = 0 or filterlen/2 if (!n) continue; for (int i = 0; i < n; i++) { // update the basic sample counter used for morse timing ++smpl_ctr; if (smpl_ctr % params.dec_ratio) continue; // decimate by DEC_RATIO // demodulate FFTvalue = zp[i].mag(); // run envelope filter FFTvalue = filter(FFTvalue,params.bfv); process_data(FFTvalue); } // for (i =0; i < n ... } //while (len-- > 0) } static void decode_sndfile (SNDFILE *infile, SF_INFO info) { static double time_domain [10 * MAX_HEIGHT] ; static double freq_domain [10 * MAX_HEIGHT] ; static double single_mag_spec [5 * MAX_HEIGHT] ; static float mag_spec [MAX_WIDTH][MAX_HEIGHT] ; static struct PEAKS p; static int once=1; fftw_plan plan ; double x, max_mag = 0.0 ; int width = 8192, height= 32, w, speclen ; int f,sr,sc =0,c,num_items,num,i,j; double *buf; int bfv; // bit filter value // speclen should be multiple of 2^n but also keep time resolution < 5 ms // for 48Khz sampling rate speclen 64 represents 2.6667 ms sample time?? // /* Print some of the info, and figure out how much data to read. */ f = info.frames; sr = info.samplerate; params.sample_rate = info.samplerate; params.dec_ratio = info.samplerate / BAYES_RATE; c = info.channels; num_items = f*c; // bit filter based on 10 msec rise time of CW waveform or manually set if (params.bfv !=0) bfv = params.bfv; else // bit filter based on 10 msec rise time of CW waveform bfv = (int)(sr * .010 / params.dec_ratio); // bfv = sr/100; // Samplerate / 100 => bfv value required for 10 msec rise time speclen = params.speclen; p.delta = params.delta; //params.sample_duration= (2.0*speclen*1000.0)/((double)16*sr); if (!params.print_text) { printf("# frames=%d\n",f); printf("# channels=%d\n",c); printf("# samplerate=%d\n",sr); printf("# dec_ratio=%d\n",params.dec_ratio); printf("# bit filter=%d\n",bfv); printf("# num_items=%d\n",num_items); printf("# sample_duration=%f\n",params.sample_duration); printf("# bitfilter=%d\n",params.bfv); printf("# speed(WPM):%d\n",params.speed); printf("# FFT filter bandwidth %f\n",2.0* params.speed/1.2); } if (params.fft) { // speclen = 16; // 16 ok for 4000 Hz test60db.wav @16384 width // 64 for 48000 Hz capture.wav @4096 w or 2048 width = params.width; if (2 * speclen > ARRAY_LEN (time_domain)) { printf ("%s : 2 * speclen > ARRAY_LEN (time_domain)\n", __func__) ; exit (1) ; } ; // fprintf(stderr,"\nsamplerate:%d\nwidth:%d\nspeclen:%d\nsample duration:%f\n", samplerate,width, speclen,params.sample_duration); plan = fftw_plan_r2r_1d (2 * speclen, time_domain, freq_domain, FFTW_R2HC, FFTW_MEASURE | FFTW_PRESERVE_INPUT) ; if (plan == NULL) { printf ("%s : line %d : create plan failed.\n", __FILE__, __LINE__) ; exit (1) ; } ; for (w = 0 ; w < width ; w++) { double single_max ; read_mono_audio (infile, info.frames, time_domain, 2 * speclen, w, width) ; apply_window (time_domain, 2 * speclen) ; fftw_execute (plan) ; single_max = calc_magnitude (freq_domain, 2 * speclen, single_mag_spec) ; // print one FFT row - just to check if (1) { //was once printf("\nmax:%f", single_max); for (int i=0; i< speclen; i++) printf("\n%f",single_mag_spec[i]); once=0; } // detect peaks in the FFT spectrum peak_detect(single_mag_spec, speclen, &p); // print found freq peaks - only once if (1 ) { for (i=0; i \n\n", progname) ; puts ( " Options include:\n\n" " -var Print variables for post-analysis (default off).\n" " -sym Print symbols before translation (default off). \n" " -txt Print decoded and translated text (default off).\n" " -txf Process text file instead of soundfile.\n" " -agc Use Automatic Gain Control (default off).\n" " -bfv Bit filter value (default 10 msec).\n" " -frq CW signal frequency (default 600 Hz).\n" " -fft Enable FFT filtering (default 0 - off) \n" " -plt Plot envelope using xplot: ./morse -plt | xplot \n" " -prs Plot speed (WPM)using xplot: ./morse -plt | xplot \n" " -spd Set default speed in WPM for decoder (default 20 ) \n" " -len Window length for FFT [8,16,32,64,128...].\n" " -wid Width of buffer to read & process [8192, 16384].\n" " -dur Sample duration in msec [5.0] used in path.c.\n" " -del Peak detection delta [10.0].\n" ) ; puts ( " -txf: The text file contains real valued input (1 datapoint per line) to be decoded.\n" " Default: The sound file type is determined by the file name extension which should be one\n" " of 'wav', 'aifc', 'aif', 'aiff', 'au', 'caf' and 'w64'.\n" ) ; exit (0) ; } /* usage_exit */ // MAIN PROGRAM = BAYESIAN MORSE DECODER // (c) Mauri Niininen AG1LE // int main(int argc, const char* argv[]) { /* Initialized data */ int k; int s_stop(char *, ftnlen); /* Local variables */ int res; FILE *fp; /* PARSE PARAMETERS FROM COMMAND LINE */ if (argc < 2) usage_exit (argv [0]) ; for (k = 1 ; k < argc; k++) { if (strcmp (argv [k], "-bfv") == 0){ k++ ; params.bfv = atoi (argv [k]) ; continue ; } if (strcmp (argv [k], "-wid") == 0){ k++ ; params.width = atoi (argv [k]) ; continue ; } if (strcmp (argv [k], "-len") == 0) { k++ ; params.speclen = atoi (argv [k]) ; continue ; } if (strcmp (argv [k], "-fft") == 0){ k++ ; params.fft = atoi (argv [k]) ; continue ; } if (strcmp (argv [k], "-dur") == 0){ k++ ; params.sample_duration = atof (argv [k]) ; continue ; } if (strcmp (argv [k], "-del") == 0){ k++ ; params.delta = atof (argv [k]) ; continue ; } if (strcmp (argv [k], "-spd") == 0){ k++ ; params.speed = atoi (argv [k]) ; continue ; } if (strcmp (argv [k], "-frq") == 0){ k++ ; params.frequency = atof (argv [k]) ; continue ; } if (strcmp (argv [k], "-amp") == 0){ k++ ; params.amplify = atof (argv [k]) ; continue ; } if (strcmp (argv [k], "-var") == 0){ params.print_variables = TRUE; continue ; } if (strcmp (argv [k], "-sym") == 0){ params.print_symbols = TRUE; continue ; } if (strcmp (argv [k], "-agc") == 0){ params.agc = TRUE; continue ; } if (strcmp (argv [k], "-txt") == 0){ params.print_text = TRUE; continue ; } if (strcmp (argv [k], "-txf") == 0){ params.process_textfile = TRUE; continue ; } if (strcmp (argv [k], "-plt") == 0){ params.print_xplot = TRUE; continue ; } if (strcmp (argv [k], "-prs") == 0){ params.print_speed = TRUE; continue ; } } ; /* INITIALIZE DATA STRUCTURES */ if(params.process_textfile) process_textfile((char *)argv[k-1]); else process_sndfile((char *)argv[k-1]); } /* MAIN__ */