/* * fsk.c * * Copyright (C) 2011-2016 Kamal Mostafa * * 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 . */ #include #include #include // fabsf, hypotf #include // FLT_EPSILON #include #include #include #include #include "fsk.h" fsk_plan * fsk_plan_new( float sample_rate, float f_mark, float f_space, float filter_bw ) { fsk_plan *fskp = malloc(sizeof(fsk_plan)); if ( !fskp ) return NULL; fskp->sample_rate = sample_rate; fskp->f_mark = f_mark; fskp->f_space = f_space; #ifdef USE_FFT fskp->band_width = filter_bw; float fft_half_bw = fskp->band_width / 2.0f; fskp->fftsize = (sample_rate + fft_half_bw) / fskp->band_width; fskp->nbands = fskp->fftsize / 2 + 1; fskp->b_mark = (f_mark + fft_half_bw) / fskp->band_width; fskp->b_space = (f_space + fft_half_bw) / fskp->band_width; if ( fskp->b_mark >= fskp->nbands || fskp->b_space >= fskp->nbands ) { fprintf(stderr, "b_mark=%u or b_space=%u is invalid (nbands=%u)\n", fskp->b_mark, fskp->b_space, fskp->nbands); free(fskp); errno = EINVAL; return NULL; } debug_log("### b_mark=%u b_space=%u fftsize=%u\n", fskp->b_mark, fskp->b_space, fskp->fftsize); // FIXME: unsigned int pa_nchannels = 1; // FIXME check these: fskp->fftin = fftwf_malloc(fskp->fftsize * sizeof(float) * pa_nchannels); bzero(fskp->fftin, (fskp->fftsize * sizeof(float) * pa_nchannels)); fskp->fftout = fftwf_malloc(fskp->nbands * sizeof(fftwf_complex) * pa_nchannels); /* complex fftw plan, works for N channels: */ fskp->fftplan = fftwf_plan_many_dft_r2c( /*rank*/1, &fskp->fftsize, /*howmany*/pa_nchannels, fskp->fftin, NULL, /*istride*/pa_nchannels, /*idist*/1, fskp->fftout, NULL, /*ostride*/1, /*odist*/fskp->nbands, FFTW_ESTIMATE); if ( !fskp->fftplan ) { fprintf(stderr, "fftwf_plan_dft_r2c_1d() failed\n"); fftwf_free(fskp->fftin); fftwf_free(fskp->fftout); free(fskp); errno = EINVAL; return NULL; } #endif return fskp; } void fsk_plan_destroy( fsk_plan *fskp ) { fftwf_free(fskp->fftin); fftwf_free(fskp->fftout); fftwf_destroy_plan(fskp->fftplan); free(fskp); } static inline float band_mag( fftwf_complex * const cplx, unsigned int band, float scalar ) { float re = cplx[band][0]; float im = cplx[band][1]; float mag = hypotf(re, im) * scalar; return mag; } static void fsk_bit_analyze( fsk_plan *fskp, float *samples, unsigned int bit_nsamples, unsigned int *bit_outp, float *bit_signal_mag_outp, float *bit_noise_mag_outp ) { // FIXME: Fast and loose ... don't bzero fftin, just assume its only ever // been used for bit_nsamples so the remainder is still zeroed. Sketchy. // // unsigned int pa_nchannels = 1; // FIXME // bzero(fskp->fftin, (fskp->fftsize * sizeof(float) * pa_nchannels)); memcpy(fskp->fftin, samples, bit_nsamples * sizeof(float)); float magscalar = 2.0f / (float)bit_nsamples; #if 0 //// apodization window //// raised cosine windows # if 0 float a0=0.54, a1=(1.0f - a0); // Hamming window # else float a0=0.5, a1=0.5; // Hann window # endif magscalar /= a0; // true for all raised cosine windows (I think) int i; for ( i=0; ifftin[i] *= w; } #endif fftwf_execute(fskp->fftplan); float mag_mark = band_mag(fskp->fftout, fskp->b_mark, magscalar); float mag_space = band_mag(fskp->fftout, fskp->b_space, magscalar); // mark==1, space==0 if ( mag_mark > mag_space ) { *bit_outp = 1; *bit_signal_mag_outp = mag_mark; *bit_noise_mag_outp = mag_space; } else { *bit_outp = 0; *bit_signal_mag_outp = mag_space; *bit_noise_mag_outp = mag_mark; } debug_log( "\t%.2f %.2f %s bit=%u sig=%.2f noise=%.2f\n", mag_mark, mag_space, mag_mark > mag_space ? "mark " : " space", *bit_outp, *bit_signal_mag_outp, *bit_noise_mag_outp); } /* returns confidence value [0.0 to INFINITY] */ static float fsk_frame_analyze( fsk_plan *fskp, float *samples, float samples_per_bit, int n_bits, const char *expect_bits_string, unsigned long long *bits_outp, float *ampl_outp ) { unsigned int bit_nsamples = (float)(samples_per_bit + 0.5f); unsigned int bit_values[64]; float bit_sig_mags[64]; float bit_noise_mags[64]; unsigned int bit_begin_sample; int bitnum; // various deprecated noise limiter schemes: //#define FSK_MIN_BIT_SNR 1.4 //#define FSK_MIN_MAGNITUDE 0.10 //#define FSK_AVOID_TRANSIENTS 0.7 const char *expect_bits = expect_bits_string; /* pass #1 - process and check only the "required" (1/0) expect_bits */ for ( bitnum=0; bitnum (s_mag * FSK_AVOID_TRANSIENTS) ) { debug_log(" avoid transient\n"); return 0.0; } #endif /* pass #2 - process only the dontcare ('d') expect_bits */ for ( bitnum=0; bitnum FLT_EPSILON ) total_bit_noise += bit_noise_mags[bitnum]; if ( bit_values[bitnum] == 1 ) { avg_mark_sig += bit_sig_mags[bitnum]; n_mark++; } else { avg_space_sig += bit_sig_mags[bitnum]; n_space++; } } // Compute the "frame SNR" float snr = total_bit_sig / total_bit_noise; // Compute avg bit sig and noise magnitudes float avg_bit_sig = total_bit_sig / n_bits; // Compute separate avg bit sig for mark and space if ( n_mark ) avg_mark_sig /= n_mark; if ( n_space ) avg_space_sig /= n_space; #if CONFIDENCE_ALGO == 6 // Compute average "divergence": bit_mag_divergence / other_bits_mag float divergence = 0.0; for ( bitnum=0; bitnum= (int)try_max_nsamples ) break; if ( t < 0 ) continue; float c, ampl_out; unsigned long long bits_out = 0; debug_log("try fsk_frame_analyze at t=%d\n", t); c = fsk_frame_analyze(fskp, samples+t, samples_per_bit, expect_n_bits, expect_bits_string, &bits_out, &l_out); if ( best_c < c ) { best_t = t; best_c = c; best_a = ampl_out; best_bits = bits_out; // If we find a frame with confidence > try_confidence_search_limit // quit searching. if ( best_c >= try_confidence_search_limit ) break; } } *bits_outp = best_bits; *ampl_outp = best_a; *frame_start_outp = best_t; float confidence = best_c; if ( confidence == 0 ) return 0; #ifdef FSK_DEBUG unsigned char bitchar; // FIXME? hardcoded chop off framing bits for debug // Hmmm... we have now way to distinguish between: // 8-bit data with no start/stopbits == 8 bits // 5-bit with prevstop+start+stop == 8 bits switch ( expect_n_bits ) { case 11: bitchar = ( *bits_outp >> 2 ) & 0xFF; break; case 8: default: bitchar = *bits_outp & 0xFF; break; } debug_log("FSK_FRAME bits='"); for ( j=0; j> j ) & 1 ? '1' : '0' ); debug_log("' datum='%c' (0x%02x) c=%f a=%f t=%d\n", isprint(bitchar)||isspace(bitchar) ? bitchar : '.', bitchar, confidence, best_a, best_t); #endif return confidence; } // #define FSK_AUTODETECT_MIN_FREQ 600 // #define FSK_AUTODETECT_MAX_FREQ 5000 int fsk_detect_carrier(fsk_plan *fskp, float *samples, unsigned int nsamples, float min_mag_threshold ) { assert( nsamples <= fskp->fftsize ); unsigned int pa_nchannels = 1; // FIXME bzero(fskp->fftin, (fskp->fftsize * sizeof(float) * pa_nchannels)); memcpy(fskp->fftin, samples, nsamples * sizeof(float)); fftwf_execute(fskp->fftplan); float magscalar = 1.0f / ((float)nsamples/2.0f); float max_mag = 0.0; int max_mag_band = -1; int i = 1; /* start detection at the first non-DC band */ int nbands = fskp->nbands; #ifdef FSK_AUTODETECT_MIN_FREQ i = (FSK_AUTODETECT_MIN_FREQ + (fskp->band_width/2)) / fskp->band_width; #endif #ifdef FSK_AUTODETECT_MAX_FREQ nbands = (FSK_AUTODETECT_MAX_FREQ + (fskp->band_width/2)) / fskp->band_width; if ( nbands > fskp->nbands ) nbands = fskp->nbands: #endif for ( ; ifftout, i, magscalar); if ( mag < min_mag_threshold ) continue; if ( max_mag < mag ) { max_mag = mag; max_mag_band = i; } } if ( max_mag_band < 0 ) return -1; return max_mag_band; } void fsk_set_tones_by_bandshift( fsk_plan *fskp, unsigned int b_mark, int b_shift ) { assert( b_shift != 0 ); assert( b_mark < fskp->nbands ); int b_space = b_mark + b_shift; assert( b_space >= 0 ); assert( b_space < fskp->nbands ); fskp->b_mark = b_mark; fskp->b_space = b_space; fskp->f_mark = b_mark * fskp->band_width; fskp->f_space = b_space * fskp->band_width; }