/* 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; } #ifdef NEON_OPTS #pragma message "We have a faster fir_decimate_cc now." //max help: http://community.arm.com/groups/android-community/blog/2015/03/27/arm-neon-programming-quick-reference 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; register float acci=0; register float accq=0; register int ti=0; register float* pinput=(float*)&(input[i+ti]); register float* ptaps=taps; register float* ptaps_end=taps+taps_length; float quad_acciq [8]; /* q0, q1: input signal I sample and Q sample q2: taps q4, q5: accumulator for I branch and Q branch (will be the output) */ asm volatile( " vmov.f32 q4, #0.0\n\t" //another way to null the accumulators " vmov.f32 q5, #0.0\n\t" "for_fdccasm: vld2.32 {q0-q1}, [%[pinput]]!\n\t" //load q0 and q1 directly from the memory address stored in pinput, with interleaving (so that we get the I samples in q0 and the Q samples in q1), also increment the memory address in pinput (hence the "!" mark) //http://community.arm.com/groups/processors/blog/2010/03/17/coding-for-neon--part-1-load-and-stores " vld1.32 {q2}, [%[ptaps]]!\n\t" " vmla.f32 q4, q0, q2\n\t" //quad_acc_i += quad_input_i * quad_taps_1 //http://stackoverflow.com/questions/3240440/how-to-use-the-multiply-and-accumulate-intrinsics-in-arm-cortex-a8 //http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0489e/CIHEJBIE.html " vmla.f32 q5, q1, q2\n\t" //quad_acc_q += quad_input_q * quad_taps_1 " cmp %[ptaps], %[ptaps_end]\n\t" //if(ptaps == ptaps_end) " bcc for_fdccasm\n\t" // then goto for_fdcasm " vst1.32 {q4}, [%[quad_acci]]\n\t" //if the loop is finished, store the two accumulators in memory " vst1.32 {q5}, [%[quad_accq]]\n\t" : [pinput]"+r"(pinput), [ptaps]"+r"(ptaps) //output operand list : [ptaps_end]"r"(ptaps_end), [quad_acci]"r"(quad_acciq), [quad_accq]"r"(quad_acciq+4) //input operand list : "memory", "q0", "q1", "q2", "q4", "q5", "cc" //clobber list ); //original for loops for reference: //for(int ti=0; ti> [%d] %g \n", n, quad_acciq[n]); iof(output,oi)=quad_acciq[0]+quad_acciq[1]+quad_acciq[2]+quad_acciq[3]; //we're still not ready, as we have to add up the contents of a quad accumulator register to get a single accumulated value qof(output,oi)=quad_acciq[4]+quad_acciq[5]+quad_acciq[6]+quad_acciq[7]; oi++; } return oi; } #else 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; float acci=0; int taps_halflength = taps_length/2; 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; //fprintf(stderr, "target_gain: %g\n",target_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;iPI) phase-=2*PI; while(phase<=-PI) phase+=2*PI; iof(output,i)=cos(phase); qof(output,i)=sin(phase); } return phase; } void fixed_amplitude_cc(complexf* input, complexf* output, int input_size, float new_amplitude) { for(int i=0;i 0) ? new_amplitude / amplitude_now : 0; iof(output,i)=iof(input,i)*gain; qof(output,i)=qof(input,i)*gain; } } /* ______ _ ______ _ _______ __ | ____| | | | ____| (_) |__ __| / _| | |__ __ _ ___| |_ | |__ ___ _ _ _ __ _ ___ _ __ | |_ __ __ _ _ __ ___| |_ ___ _ __ _ __ ___ | __/ _` / __| __| | __/ _ \| | | | '__| |/ _ \ '__| | | '__/ _` | '_ \/ __| _/ _ \| '__| '_ ` _ \ | | | (_| \__ \ |_ | | | (_) | |_| | | | | __/ | | | | | (_| | | | \__ \ || (_) | | | | | | | | |_| \__,_|___/\__| |_| \___/ \__,_|_| |_|\___|_| |_|_| \__,_|_| |_|___/_| \___/|_| |_| |_| |_| */ int log2n(int x) { int result=-1; for(int i=0;i<31;i++) { if((x>>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_s8(float* input, signed char* 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>8); unsigned char* ptemp=(unsigned char*)&temp; output[k++]=*ptemp; output[k++]=*(ptemp+1); output[k++]=*(ptemp+2); } else for(int i=0;i>8); unsigned char* ptemp=(unsigned char*)&temp; output[k++]=*(ptemp+2); output[k++]=*(ptemp+1); output[k++]=*ptemp; } } void convert_s24_f(unsigned char* input, float* output, int input_size, int bigendian) { int k=0; if(bigendian) for(int i=0;i