/* This software is part of libcsdr, a set of simple DSP routines for Software Defined Radio. Copyright (c) 2014, Andras Retzler All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANDRAS RETZLER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include "libcsdr.h" #include "predefined.h" #include /* _ _ __ _ _ (_) | | / _| | | (_) __ ___ _ __ __| | _____ __ | |_ _ _ _ __ ___| |_ _ ___ _ __ ___ \ \ /\ / / | '_ \ / _` |/ _ \ \ /\ / / | _| | | | '_ \ / __| __| |/ _ \| '_ \/ __| \ V V /| | | | | (_| | (_) \ V V / | | | |_| | | | | (__| |_| | (_) | | | \__ \ \_/\_/ |_|_| |_|\__,_|\___/ \_/\_/ |_| \__,_|_| |_|\___|\__|_|\___/|_| |_|___/ */ #define MFIRDES_GWS(NAME) \ if(!strcmp( #NAME , input )) return WINDOW_ ## NAME; window_t firdes_get_window_from_string(char* input) { MFIRDES_GWS(BOXCAR); MFIRDES_GWS(BLACKMAN); MFIRDES_GWS(HAMMING); return WINDOW_DEFAULT; } #define MFIRDES_GSW(NAME) \ if(window == WINDOW_ ## NAME) return #NAME; char* firdes_get_string_from_window(window_t window) { MFIRDES_GSW(BOXCAR); MFIRDES_GSW(BLACKMAN); MFIRDES_GSW(HAMMING); return "INVALID"; } float firdes_wkernel_blackman(float rate) { //Explanation at Chapter 16 of dspguide.com, page 2 //Blackman window has better stopband attentuation and passband ripple than Hamming, but it has slower rolloff. rate=0.5+rate/2; return 0.42-0.5*cos(2*PI*rate)+0.08*cos(4*PI*rate); } float firdes_wkernel_hamming(float rate) { //Explanation at Chapter 16 of dspguide.com, page 2 //Hamming window has worse stopband attentuation and passband ripple than Blackman, but it has faster rolloff. rate=0.5+rate/2; return 0.54-0.46*cos(2*PI*rate); } float firdes_wkernel_boxcar(float rate) { //"Dummy" window kernel, do not use; an unwindowed FIR filter may have bad frequency response return 1.0; } float (*firdes_get_window_kernel(window_t window))(float) { if(window==WINDOW_HAMMING) return firdes_wkernel_hamming; else if(window==WINDOW_BLACKMAN) return firdes_wkernel_blackman; else if(window==WINDOW_BOXCAR) return firdes_wkernel_boxcar; else return firdes_get_window_kernel(WINDOW_DEFAULT); } /* ______ _____ _____ __ _ _ _ _ _ | ____|_ _| __ \ / _(_) | | | | (_) | |__ | | | |__) | | |_ _| | |_ ___ _ __ __| | ___ ___ _ __ _ _ __ | __| | | | _ / | _| | | __/ _ \ '__| / _` |/ _ \/ __| |/ _` | '_ \ | | _| |_| | \ \ | | | | | || __/ | | (_| | __/\__ \ | (_| | | | | |_| |_____|_| \_\ |_| |_|_|\__\___|_| \__,_|\___||___/_|\__, |_| |_| __/ | |___/ */ void firdes_lowpass_f(float *output, int length, float cutoff_rate, window_t window) { //Generates symmetric windowed sinc FIR filter real taps // length should be odd // cutoff_rate is (cutoff frequency/sampling frequency) //Explanation at Chapter 16 of dspguide.com int middle=length/2; float temp; float (*window_function)(float) = firdes_get_window_kernel(window); output[middle]=2*PI*cutoff_rate*window_function(0); for(int i=1; i<=middle; i++) //@@firdes_lowpass_f: calculate taps { output[middle-i]=output[middle+i]=(sin(2*PI*cutoff_rate*i)/i)*window_function((float)i/middle); //printf("%g %d %d %d %d | %g\n",output[middle-i],i,middle,middle+i,middle-i,sin(2*PI*cutoff_rate*i)); } //Normalize filter kernel float sum=0; for(int i=0;i2*PI) phase-=2*PI; //@@firdes_bandpass_c while(phase<0) phase+=2*PI; iof(output,i)=cosval*realtaps[i]; qof(output,i)=sinval*realtaps[i]; //output[i] := realtaps[i] * e^j*w } } int firdes_filter_len(float transition_bw) { int result=4.0/transition_bw; if (result%2==0) result++; //number of symmetric FIR filter taps should be odd return result; } /* _____ _____ _____ __ _ _ | __ \ / ____| __ \ / _| | | (_) | | | | (___ | |__) | | |_ _ _ _ __ ___| |_ _ ___ _ __ ___ | | | |\___ \| ___/ | _| | | | '_ \ / __| __| |/ _ \| '_ \/ __| | |__| |____) | | | | | |_| | | | | (__| |_| | (_) | | | \__ \ |_____/|_____/|_| |_| \__,_|_| |_|\___|\__|_|\___/|_| |_|___/ */ float shift_math_cc(complexf *input, complexf* output, int input_size, float rate, float starting_phase) { rate*=2; //Shifts the complex spectrum. Basically a complex mixer. This version uses cmath. float phase=starting_phase; float phase_increment=rate*PI; float cosval, sinval; for(int i=0;i2*PI) phase-=2*PI; //@shift_math_cc: normalize phase while(phase<0) phase+=2*PI; } return phase; } shift_table_data_t shift_table_init(int table_size) { //RTODO shift_table_data_t output; output.table=(float*)malloc(sizeof(float)*table_size); output.table_size=table_size; for(int i=0;i1)?-1:1; //in quadrant 2 and 3 cos_sign=(quadrant&&quadrant<3)?-1:1; //in quadrant 1 and 2 sinval=sin_sign*table_data.table[sin_index]; cosval=cos_sign*table_data.table[cos_index]; //we multiply two complex numbers. //how? enter this to maxima (software) for explanation: // (a+b*%i)*(c+d*%i), rectform; iof(output,i)=cosval*iof(input,i)-sinval*qof(input,i); qof(output,i)=sinval*iof(input,i)+cosval*qof(input,i); phase+=phase_increment; while(phase>2*PI) phase-=2*PI; //@shift_math_cc: normalize phase while(phase<0) phase+=2*PI; } return phase; } int fir_decimate_cc(complexf *input, complexf *output, int input_size, int decimation, float *taps, int taps_length) { //Theory: http://www.dspguru.com/dsp/faqs/multirate/decimation //It uses real taps. It returns the number of output samples actually written. //It needs overlapping input based on its returned value: //number of processed input samples = returned value * decimation factor //The output buffer should be at least input_length / 3. // i: input index | ti: tap index | oi: output index int oi=0; for(int i=0; iinput_size) break; float acci=0; for(int ti=0; tiinput_size) break; //we can't compute the FIR filter to some input samples at the end //fprintf(stderr,"outer loop | oi = %d | startingi = %d | taps delay = %d\n",oi,startingi,delayi); for(int i=0; i<(taps_length-delayi)/interpolation; i++) //@rational_resampler_ff (inner loop) { //fprintf(stderr,"inner loop | input index = %d | tap index = %d | acc = %g\n",startingi+ii,i,acc); acc+=input[startingi+i]*taps[delayi+i*interpolation]; } output[oi]=acc*interpolation; } rational_resampler_ff_t d; d.input_processed=startingi; d.output_size=oi; d.last_taps_delay=delayi; return d; } /* The greatest challenge in resampling is figuring out which tap should be applied to which sample. Typical test patterns for rational_resampler_ff: interpolation = 3, decimation = 1 values of [oi, startingi, taps delay] in the outer loop should be: 0 0 0 1 1 2 2 1 1 3 1 0 4 2 2 5 2 1 interpolation = 3, decimation = 2 values of [oi, startingi, taps delay] in the outer loop should be: 0 0 0 1 1 1 2 2 2 3 2 0 4 3 1 5 4 2 */ void rational_resampler_get_lowpass_f(float* output, int output_size, int interpolation, int decimation, window_t window) { //See 4.1.6 at: http://www.dspguru.com/dsp/faqs/multirate/resampling float cutoff_for_interpolation=1.0/interpolation; float cutoff_for_decimation=1.0/decimation; float cutoff = (cutoff_for_interpolationoutput; complexf* out = plan_inverse->input; for(int i=0;isize;i++) //@apply_fir_fft_cc: multiplication { iof(out,i)=iof(in,i)*iof(taps_fft,i)-qof(in,i)*qof(taps_fft,i); qof(out,i)=iof(in,i)*qof(taps_fft,i)+qof(in,i)*iof(taps_fft,i); } //calculate inverse FFT on multiplied buffer fft_execute(plan_inverse); //add the overlap of the previous segment complexf* result = plan_inverse->output; for(int i=0;isize;i++) //@apply_fir_fft_cc: normalize by fft_size { iof(result,i)/=plan->size; qof(result,i)/=plan->size; } for(int i=0;imax_iq) max_iq=abs_q; float min_iq=abs_i; if(abs_qinput_size;i++) //@fastagc_ff: peak search { float val=fabs(input->buffer_input[i]); if(val>peak_input) peak_input=val; } //Determine the maximal peak out of the three blocks float target_peak=peak_input; if(target_peakpeak_2) target_peak=input->peak_2; if(target_peakpeak_1) target_peak=input->peak_1; //we change the gain linearly on the apply_block from the last_gain to target_gain. float target_gain=input->reference/target_peak; if(target_gain>FASTAGC_MAX_GAIN) target_gain=FASTAGC_MAX_GAIN; for(int i=0;iinput_size;i++) //@fastagc_ff: apply gain { float rate=(float)i/input->input_size; float gain=input->last_gain*(1.0-rate)+target_gain*rate; output[i]=input->buffer_1[i]*gain; } //Shift the three buffers float* temp_pointer=input->buffer_1; input->buffer_1=input->buffer_2; input->peak_1=input->peak_2; input->buffer_2=input->buffer_input; input->peak_2=peak_input; input->buffer_input=temp_pointer; input->last_gain=target_gain; //fprintf(stderr,"target_gain=%g\n", target_gain); } /* ______ __ __ _ _ _ _ | ____| \/ | | | | | | | | | | |__ | \ / | __| | ___ _ __ ___ ___ __| |_ _| | __ _| |_ ___ _ __ ___ | __| | |\/| | / _` |/ _ \ '_ ` _ \ / _ \ / _` | | | | |/ _` | __/ _ \| '__/ __| | | | | | | | (_| | __/ | | | | | (_) | (_| | |_| | | (_| | || (_) | | \__ \ |_| |_| |_| \__,_|\___|_| |_| |_|\___/ \__,_|\__,_|_|\__,_|\__\___/|_| |___/ */ float fmdemod_atan_cf(complexf* input, float *output, int input_size, float last_phase) { //GCC most likely won't vectorize nor atan, nor atan2. //For more comments, look at: https://github.com/simonyiszk/minidemod/blob/master/minidemod-wfm-atan.c float phase, dphase; for (int i=0; iPI) dphase-=2*PI; output[i]=dphase/PI; last_phase=phase; } return last_phase; } #define fmdemod_quadri_K 0.340447550238101026565118445432744920253753662109375 //this constant ensures proper scaling for qa_fmemod testcases for SNR calculation and more. complexf fmdemod_quadri_novect_cf(complexf* input, float* output, int input_size, complexf last_sample) { output[0]=fmdemod_quadri_K*(iof(input,0)*(qof(input,0)-last_sample.q)-qof(input,0)*(iof(input,0)-last_sample.i))/(iof(input,0)*iof(input,0)+qof(input,0)*qof(input,0)); for (int i=1; i tau = 75e-6 WFM transmission in EU: 50 us -> tau = 50e-6 More info at: http://www.cliftonlaboratories.com/fm_receivers_and_de-emphasis.htm Simulate in octave: tau=75e-6; dt=1/48000; alpha = dt/(tau+dt); freqz([alpha],[1 -(1-alpha)]) */ float dt = 1.0/sample_rate; float alpha = dt/(tau+dt); if(is_nan(last_output)) last_output=0.0; //if last_output is NaN output[0]=alpha*input[0]+(1-alpha)*last_output; for (int i=1;ioutput[i])?-max_amplitude:output[i]; } } void gain_ff(float* input, float* output, int input_size, float gain) { for(int i=0;i>i)&1) //@@log2n { if (result==-1) result=i; else return -1; } } return result; } int next_pow2(int x) { int pow2; //portability? (31 is the problem) for(int i=0;i<31;i++) { if(x<(pow2=1< convert_u8_f } void convert_f_i16(float* input, short* output, int input_size) { /*for(int i=0;i1.0) input[i]=1.0; if(input[i]<-1.0) input[i]=-1.0; }*/ for(int i=0;i