/* 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 #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 normalize_fir_f(float* input, float* output, int length) { //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; } shift_unroll_data_t shift_unroll_init(float rate, int size) { shift_unroll_data_t output; output.phase_increment=2*rate*PI; output.size = size; output.dsin=(float*)malloc(sizeof(float)*size); output.dcos=(float*)malloc(sizeof(float)*size); float myphase = 0; for(int i=0;iPI) myphase-=2*PI; while(myphase<-PI) myphase+=2*PI; output.dsin[i]=sin(myphase); output.dcos[i]=cos(myphase); } return output; } float shift_unroll_cc(complexf *input, complexf* output, int input_size, shift_unroll_data_t* d, float starting_phase) { //input_size should be multiple of 4 //fprintf(stderr, "shift_addfast_cc: input_size = %d\n", input_size); float cos_start=cos(starting_phase); float sin_start=sin(starting_phase); register float cos_val, sin_val; for(int i=0;idcos[i] - sin_start * d->dsin[i]; sin_val = sin_start * d->dcos[i] + cos_start * d->dsin[i]; iof(output,i)=cos_val*iof(input,i)-sin_val*qof(input,i); qof(output,i)=sin_val*iof(input,i)+cos_val*qof(input,i); } starting_phase+=input_size*d->phase_increment; while(starting_phase>PI) starting_phase-=2*PI; while(starting_phase<-PI) starting_phase+=2*PI; return starting_phase; } shift_addfast_data_t shift_addfast_init(float rate) { shift_addfast_data_t output; output.phase_increment=2*rate*PI; for(int i=0;i<4;i++) { output.dsin[i]=sin(output.phase_increment*(i+1)); output.dcos[i]=cos(output.phase_increment*(i+1)); } return output; } #ifdef NEON_OPTS #pragma message "Manual NEON optimizations are ON: we have a faster shift_addfast_cc now." float shift_addfast_cc(complexf *input, complexf* output, int input_size, shift_addfast_data_t* d, float starting_phase) { //input_size should be multiple of 4 float cos_start[4], sin_start[4]; float cos_vals[4], sin_vals[4]; for(int i=0;i<4;i++) { cos_start[i] = cos(starting_phase); sin_start[i] = sin(starting_phase); } float* pdcos = d->dcos; float* pdsin = d->dsin; register float* pinput = (float*)input; register float* pinput_end = (float*)(input+input_size); register float* poutput = (float*)output; //Register map: #define RDCOS "q0" //dcos, dsin #define RDSIN "q1" #define RCOSST "q2" //cos_start, sin_start #define RSINST "q3" #define RCOSV "q4" //cos_vals, sin_vals #define RSINV "q5" #define ROUTI "q6" //output_i, output_q #define ROUTQ "q7" #define RINPI "q8" //input_i, input_q #define RINPQ "q9" #define R3(x,y,z) x ", " y ", " z "\n\t" asm volatile( //(the range of q is q0-q15) " vld1.32 {" RDCOS "}, [%[pdcos]]\n\t" " vld1.32 {" RDSIN "}, [%[pdsin]]\n\t" " vld1.32 {" RCOSST "}, [%[cos_start]]\n\t" " vld1.32 {" RSINST "}, [%[sin_start]]\n\t" "for_addfast: vld2.32 {" RINPI "-" RINPQ "}, [%[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 RINPI and the Q samples in RINPQ), also increment the memory address in pinput (hence the "!" mark) //C version: //cos_vals[j] = cos_start * d->dcos[j] - sin_start * d->dsin[j]; //sin_vals[j] = sin_start * d->dcos[j] + cos_start * d->dsin[j]; " vmul.f32 " R3(RCOSV, RCOSST, RDCOS) //cos_vals[i] = cos_start * d->dcos[i] " vmls.f32 " R3(RCOSV, RSINST, RDSIN) //cos_vals[i] -= sin_start * d->dsin[i] " vmul.f32 " R3(RSINV, RSINST, RDCOS) //sin_vals[i] = sin_start * d->dcos[i] " vmla.f32 " R3(RSINV, RCOSST, RDSIN) //sin_vals[i] += cos_start * d->dsin[i] //C version: //iof(output,4*i+j)=cos_vals[j]*iof(input,4*i+j)-sin_vals[j]*qof(input,4*i+j); //qof(output,4*i+j)=sin_vals[j]*iof(input,4*i+j)+cos_vals[j]*qof(input,4*i+j); " vmul.f32 " R3(ROUTI, RCOSV, RINPI) //output_i = cos_vals * input_i " vmls.f32 " R3(ROUTI, RSINV, RINPQ) //output_i -= sin_vals * input_q " vmul.f32 " R3(ROUTQ, RSINV, RINPI) //output_q = sin_vals * input_i " vmla.f32 " R3(ROUTQ, RCOSV, RINPQ) //output_i += cos_vals * input_q " vst2.32 {" ROUTI "-" ROUTQ "}, [%[poutput]]!\n\t" //store the outputs in memory //" add %[poutput],%[poutput],#32\n\t" " vdup.32 " RCOSST ", d9[1]\n\t" // cos_start[0-3] = cos_vals[3] " vdup.32 " RSINST ", d11[1]\n\t" // sin_start[0-3] = sin_vals[3] " cmp %[pinput], %[pinput_end]\n\t" //if(pinput != pinput_end) " bcc for_addfast\n\t" // then goto for_addfast : [pinput]"+r"(pinput), [poutput]"+r"(poutput) //output operand list -> C variables that we will change from ASM : [pinput_end]"r"(pinput_end), [pdcos]"r"(pdcos), [pdsin]"r"(pdsin), [sin_start]"r"(sin_start), [cos_start]"r"(cos_start) //input operand list : "memory", "q0", "q1", "q2", "q4", "q5", "q6", "q7", "q8", "q9", "cc" //clobber list ); starting_phase+=input_size*d->phase_increment; while(starting_phase>PI) starting_phase-=2*PI; while(starting_phase<-PI) starting_phase+=2*PI; return starting_phase; } #else #if 1 #define SADF_L1(j) cos_vals_ ## j = cos_start * dcos_ ## j - sin_start * dsin_ ## j; \ sin_vals_ ## j = sin_start * dcos_ ## j + cos_start * dsin_ ## j; #define SADF_L2(j) iof(output,4*i+j)=(cos_vals_ ## j)*iof(input,4*i+j)-(sin_vals_ ## j)*qof(input,4*i+j); \ qof(output,4*i+j)=(sin_vals_ ## j)*iof(input,4*i+j)+(cos_vals_ ## j)*qof(input,4*i+j); float shift_addfast_cc(complexf *input, complexf* output, int input_size, shift_addfast_data_t* d, float starting_phase) { //input_size should be multiple of 4 //fprintf(stderr, "shift_addfast_cc: input_size = %d\n", input_size); float cos_start=cos(starting_phase); float sin_start=sin(starting_phase); float register cos_vals_0, cos_vals_1, cos_vals_2, cos_vals_3, sin_vals_0, sin_vals_1, sin_vals_2, sin_vals_3, dsin_0 = d->dsin[0], dsin_1 = d->dsin[1], dsin_2 = d->dsin[2], dsin_3 = d->dsin[3], dcos_0 = d->dcos[0], dcos_1 = d->dcos[1], dcos_2 = d->dcos[2], dcos_3 = d->dcos[3]; for(int i=0;iphase_increment; while(starting_phase>PI) starting_phase-=2*PI; while(starting_phase<-PI) starting_phase+=2*PI; return starting_phase; } #else float shift_addfast_cc(complexf *input, complexf* output, int input_size, shift_addfast_data_t* d, float starting_phase) { //input_size should be multiple of 4 //fprintf(stderr, "shift_addfast_cc: input_size = %d\n", input_size); float cos_start=cos(starting_phase); float sin_start=sin(starting_phase); float cos_vals[4], sin_vals[4]; for(int i=0;idcos[j] - sin_start * d->dsin[j]; sin_vals[j] = sin_start * d->dcos[j] + cos_start * d->dsin[j]; } for(int j=0;j<4;j++) //@shift_addfast_cc { iof(output,4*i+j)=cos_vals[j]*iof(input,4*i+j)-sin_vals[j]*qof(input,4*i+j); qof(output,4*i+j)=sin_vals[j]*iof(input,4*i+j)+cos_vals[j]*qof(input,4*i+j); } cos_start = cos_vals[3]; sin_start = sin_vals[3]; } starting_phase+=input_size*d->phase_increment; while(starting_phase>PI) starting_phase-=2*PI; while(starting_phase<-PI) starting_phase+=2*PI; return starting_phase; } #endif #endif #ifdef NEON_OPTS #pragma message "Manual NEON optimizations are ON: 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* pinput=(float*)&(input[i]); 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( " veor q4, q4\n\t" " veor q5, q5\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; ti input_size*interpolation) break; for(int ip=0; ipinput_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_interpolationrate); if(DEBUG_ASSERT) assert(d->rate > 1.0); if(DEBUG_ASSERT) assert(d->where >= -d->xifirst); int oi=0; //output index int index_high; #define FD_INDEX_LOW (index_high-1) //we optimize to calculate ceilf(where) only once every iteration, so we do it here: for(;(index_high=ceilf(d->where))+d->num_poly_points+d->taps_lengthwhere+=d->rate) //@fractional_decimator_ff { //d->num_poly_points above is theoretically more than we could have here, but this makes the spectrum look good int sxifirst = FD_INDEX_LOW + d->xifirst; int sxilast = FD_INDEX_LOW + d->xilast; if(d->taps) for(int wi=0;winum_poly_points;wi++) d->filtered_buf[wi] = fir_one_pass_ff(input+FD_INDEX_LOW+wi, d->taps, d->taps_length); else for(int wi=0;winum_poly_points;wi++) d->filtered_buf[wi] = *(input+FD_INDEX_LOW+wi); int id=0; float xwhere = d->where - FD_INDEX_LOW; for(int xi=d->xifirst;xi<=d->xilast;xi++) { d->coeffs_buf[id]=1; for(int xj=d->xifirst;xj<=d->xilast;xj++) { if(xi!=xj) d->coeffs_buf[id] *= (xwhere-xj); } id++; } float acc = 0; for(int i=0;inum_poly_points;i++) { acc += (d->coeffs_buf[i]/d->poly_precalc_denomiator[i])*d->filtered_buf[i]; //(xnom/xden)*yn } output[oi++]=acc; } d->input_processed = FD_INDEX_LOW + d->xifirst; d->where -= d->input_processed; d->output_size = oi; } /* * Some notes to myself on the circular buffer I wanted to implement here: int last_input_samplewhere_shouldbe = (index_high-1)+xifirst; int last_input_offset = last_input_samplewhere_shouldbe - d->last_input_samplewhere; if(last_input_offset < num_poly_points) { //if we can move the last_input circular buffer, we move, and add the new samples at the end d->last_inputs_startsat += last_input_offset; d->last_inputs_startsat %= num_poly_points; int num_copied_samples = 0; for(int i=0; ilast_inputs_circbuf[i]= } d->last_input_samplewhere = d->las } However, I think I should just rather do a continuous big buffer. */ void apply_fir_fft_cc(FFT_PLAN_T* plan, FFT_PLAN_T* plan_inverse, complexf* taps_fft, complexf* last_overlap, int overlap_size) { //use the overlap & add method for filtering //calculate FFT on input buffer fft_execute(plan); //multiply the filter and the input complexf* in = plan->output; 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< { .code = 0b1010101111, .bitcount=10, .ascii=0x3f }, //? { .code = 0b1010111101, .bitcount=10, .ascii=0x40 }, //@ { .code = 0b1111101, .bitcount=7, .ascii=0x41 }, //A { .code = 0b11101011, .bitcount=8, .ascii=0x42 }, //B { .code = 0b10101101, .bitcount=8, .ascii=0x43 }, //C { .code = 0b10110101, .bitcount=8, .ascii=0x44 }, //D { .code = 0b1110111, .bitcount=7, .ascii=0x45 }, //E { .code = 0b11011011, .bitcount=8, .ascii=0x46 }, //F { .code = 0b11111101, .bitcount=8, .ascii=0x47 }, //G { .code = 0b101010101, .bitcount=9, .ascii=0x48 }, //H { .code = 0b1111111, .bitcount=7, .ascii=0x49 }, //I { .code = 0b111111101, .bitcount=9, .ascii=0x4a }, //J { .code = 0b101111101, .bitcount=9, .ascii=0x4b }, //K { .code = 0b11010111, .bitcount=8, .ascii=0x4c }, //L { .code = 0b10111011, .bitcount=8, .ascii=0x4d }, //M { .code = 0b11011101, .bitcount=8, .ascii=0x4e }, //N { .code = 0b10101011, .bitcount=8, .ascii=0x4f }, //O { .code = 0b11010101, .bitcount=8, .ascii=0x50 }, //P { .code = 0b111011101, .bitcount=9, .ascii=0x51 }, //Q { .code = 0b10101111, .bitcount=8, .ascii=0x52 }, //R { .code = 0b1101111, .bitcount=7, .ascii=0x53 }, //S { .code = 0b1101101, .bitcount=7, .ascii=0x54 }, //T { .code = 0b101010111, .bitcount=9, .ascii=0x55 }, //U { .code = 0b110110101, .bitcount=9, .ascii=0x56 }, //V { .code = 0b101011101, .bitcount=9, .ascii=0x57 }, //W { .code = 0b101110101, .bitcount=9, .ascii=0x58 }, //X { .code = 0b101111011, .bitcount=9, .ascii=0x59 }, //Y { .code = 0b1010101101, .bitcount=10, .ascii=0x5a }, //Z { .code = 0b111110111, .bitcount=9, .ascii=0x5b }, //[ { .code = 0b111101111, .bitcount=9, .ascii=0x5c }, //backslash { .code = 0b111111011, .bitcount=9, .ascii=0x5d }, //] { .code = 0b1010111111, .bitcount=10, .ascii=0x5e }, //^ { .code = 0b101101101, .bitcount=9, .ascii=0x5f }, //_ { .code = 0b1011011111, .bitcount=10, .ascii=0x60 }, //` { .code = 0b1011, .bitcount=4, .ascii=0x61 }, //a { .code = 0b1011111, .bitcount=7, .ascii=0x62 }, //b { .code = 0b101111, .bitcount=6, .ascii=0x63 }, //c { .code = 0b101101, .bitcount=6, .ascii=0x64 }, //d { .code = 0b11, .bitcount=2, .ascii=0x65 }, //e { .code = 0b111101, .bitcount=6, .ascii=0x66 }, //f { .code = 0b1011011, .bitcount=7, .ascii=0x67 }, //g { .code = 0b101011, .bitcount=6, .ascii=0x68 }, //h { .code = 0b1101, .bitcount=4, .ascii=0x69 }, //i { .code = 0b111101011, .bitcount=9, .ascii=0x6a }, //j { .code = 0b10111111, .bitcount=8, .ascii=0x6b }, //k { .code = 0b11011, .bitcount=5, .ascii=0x6c }, //l { .code = 0b111011, .bitcount=6, .ascii=0x6d }, //m { .code = 0b1111, .bitcount=4, .ascii=0x6e }, //n { .code = 0b111, .bitcount=3, .ascii=0x6f }, //o { .code = 0b111111, .bitcount=6, .ascii=0x70 }, //p { .code = 0b110111111, .bitcount=9, .ascii=0x71 }, //q { .code = 0b10101, .bitcount=5, .ascii=0x72 }, //r { .code = 0b10111, .bitcount=5, .ascii=0x73 }, //s { .code = 0b101, .bitcount=3, .ascii=0x74 }, //t { .code = 0b110111, .bitcount=6, .ascii=0x75 }, //u { .code = 0b1111011, .bitcount=7, .ascii=0x76 }, //v { .code = 0b1101011, .bitcount=7, .ascii=0x77 }, //w { .code = 0b11011111, .bitcount=8, .ascii=0x78 }, //x { .code = 0b1011101, .bitcount=7, .ascii=0x79 }, //y { .code = 0b111010101, .bitcount=9, .ascii=0x7a }, //z { .code = 0b1010110111, .bitcount=10, .ascii=0x7b }, //{ { .code = 0b110111011, .bitcount=9, .ascii=0x7c }, //| { .code = 0b1010110101, .bitcount=10, .ascii=0x7d }, //} { .code = 0b1011010111, .bitcount=10, .ascii=0x7e }, //~ { .code = 0b1110110101, .bitcount=10, .ascii=0x7f }, //DEL }; unsigned long long psk31_varicode_masklen_helper[] = { 0b0000000000000000000000000000000000000000000000000000000000000000, 0b0000000000000000000000000000000000000000000000000000000000000001, 0b0000000000000000000000000000000000000000000000000000000000000011, 0b0000000000000000000000000000000000000000000000000000000000000111, 0b0000000000000000000000000000000000000000000000000000000000001111, 0b0000000000000000000000000000000000000000000000000000000000011111, 0b0000000000000000000000000000000000000000000000000000000000111111, 0b0000000000000000000000000000000000000000000000000000000001111111, 0b0000000000000000000000000000000000000000000000000000000011111111, 0b0000000000000000000000000000000000000000000000000000000111111111, 0b0000000000000000000000000000000000000000000000000000001111111111, 0b0000000000000000000000000000000000000000000000000000011111111111, 0b0000000000000000000000000000000000000000000000000000111111111111, 0b0000000000000000000000000000000000000000000000000001111111111111, 0b0000000000000000000000000000000000000000000000000011111111111111, 0b0000000000000000000000000000000000000000000000000111111111111111, 0b0000000000000000000000000000000000000000000000001111111111111111, 0b0000000000000000000000000000000000000000000000011111111111111111, 0b0000000000000000000000000000000000000000000000111111111111111111, 0b0000000000000000000000000000000000000000000001111111111111111111, 0b0000000000000000000000000000000000000000000011111111111111111111, 0b0000000000000000000000000000000000000000000111111111111111111111, 0b0000000000000000000000000000000000000000001111111111111111111111, 0b0000000000000000000000000000000000000000011111111111111111111111, 0b0000000000000000000000000000000000000000111111111111111111111111, 0b0000000000000000000000000000000000000001111111111111111111111111, 0b0000000000000000000000000000000000000011111111111111111111111111, 0b0000000000000000000000000000000000000111111111111111111111111111, 0b0000000000000000000000000000000000001111111111111111111111111111, 0b0000000000000000000000000000000000011111111111111111111111111111, 0b0000000000000000000000000000000000111111111111111111111111111111, 0b0000000000000000000000000000000001111111111111111111111111111111, 0b0000000000000000000000000000000011111111111111111111111111111111, 0b0000000000000000000000000000000111111111111111111111111111111111, 0b0000000000000000000000000000001111111111111111111111111111111111, 0b0000000000000000000000000000011111111111111111111111111111111111, 0b0000000000000000000000000000111111111111111111111111111111111111, 0b0000000000000000000000000001111111111111111111111111111111111111, 0b0000000000000000000000000011111111111111111111111111111111111111, 0b0000000000000000000000000111111111111111111111111111111111111111, 0b0000000000000000000000001111111111111111111111111111111111111111, 0b0000000000000000000000011111111111111111111111111111111111111111, 0b0000000000000000000000111111111111111111111111111111111111111111, 0b0000000000000000000001111111111111111111111111111111111111111111, 0b0000000000000000000011111111111111111111111111111111111111111111, 0b0000000000000000000111111111111111111111111111111111111111111111, 0b0000000000000000001111111111111111111111111111111111111111111111, 0b0000000000000000011111111111111111111111111111111111111111111111, 0b0000000000000000111111111111111111111111111111111111111111111111, 0b0000000000000001111111111111111111111111111111111111111111111111, 0b0000000000000011111111111111111111111111111111111111111111111111, 0b0000000000000111111111111111111111111111111111111111111111111111, 0b0000000000001111111111111111111111111111111111111111111111111111, 0b0000000000011111111111111111111111111111111111111111111111111111, 0b0000000000111111111111111111111111111111111111111111111111111111, 0b0000000001111111111111111111111111111111111111111111111111111111, 0b0000000011111111111111111111111111111111111111111111111111111111, 0b0000000111111111111111111111111111111111111111111111111111111111, 0b0000001111111111111111111111111111111111111111111111111111111111, 0b0000011111111111111111111111111111111111111111111111111111111111, 0b0000111111111111111111111111111111111111111111111111111111111111, 0b0001111111111111111111111111111111111111111111111111111111111111, 0b0011111111111111111111111111111111111111111111111111111111111111, 0b0111111111111111111111111111111111111111111111111111111111111111 }; const int n_psk31_varicode_items = sizeof(psk31_varicode_items) / sizeof(psk31_varicode_item_t); char psk31_varicode_decoder_push(unsigned long long* status_shr, unsigned char symbol) { *status_shr=((*status_shr)<<1)|(!!symbol); //shift new bit in shift register //fprintf(stderr,"*status_shr = %llx\n", *status_shr); if((*status_shr)&0xFFF==0) return 0; for(int i=0;i>>>>>>>> %d %x %c\n", i, psk31_varicode_items[i].ascii, psk31_varicode_items[i].ascii);*/ return psk31_varicode_items[i].ascii; } } return 0; } void psk31_varicode_encoder_u8_u8(unsigned char* input, unsigned char* output, int input_size, int output_max_size, int* input_processed, int* output_size) { (*output_size)=0; for((*input_processed)=0; (*input_processed)>(current_varicode.bitcount-bi-1))&1 : 0; (*output_size)++; output_max_size--; } break; } } } } rtty_baudot_item_t rtty_baudot_items[] = { { .code = 0b00000, .ascii_letter=0, .ascii_figure=0 }, { .code = 0b10000, .ascii_letter='E', .ascii_figure='3' }, { .code = 0b01000, .ascii_letter='\n', .ascii_figure='\n' }, { .code = 0b11000, .ascii_letter='A', .ascii_figure='-' }, { .code = 0b00100, .ascii_letter=' ', .ascii_figure=' ' }, { .code = 0b10100, .ascii_letter='S', .ascii_figure='\'' }, { .code = 0b01100, .ascii_letter='I', .ascii_figure='8' }, { .code = 0b11100, .ascii_letter='U', .ascii_figure='7' }, { .code = 0b00010, .ascii_letter='\r', .ascii_figure='\r' }, { .code = 0b10010, .ascii_letter='D', .ascii_figure='#' }, { .code = 0b01010, .ascii_letter='R', .ascii_figure='4' }, { .code = 0b11010, .ascii_letter='J', .ascii_figure='\a' }, { .code = 0b00110, .ascii_letter='N', .ascii_figure=',' }, { .code = 0b10110, .ascii_letter='F', .ascii_figure='@' }, { .code = 0b01110, .ascii_letter='C', .ascii_figure=':' }, { .code = 0b11110, .ascii_letter='K', .ascii_figure='(' }, { .code = 0b00001, .ascii_letter='T', .ascii_figure='5' }, { .code = 0b10001, .ascii_letter='Z', .ascii_figure='+' }, { .code = 0b01001, .ascii_letter='L', .ascii_figure=')' }, { .code = 0b11001, .ascii_letter='W', .ascii_figure='2' }, { .code = 0b00101, .ascii_letter='H', .ascii_figure='$' }, { .code = 0b10101, .ascii_letter='Y', .ascii_figure='6' }, { .code = 0b01101, .ascii_letter='P', .ascii_figure='0' }, { .code = 0b11101, .ascii_letter='Q', .ascii_figure='1' }, { .code = 0b00011, .ascii_letter='O', .ascii_figure='9' }, { .code = 0b10011, .ascii_letter='B', .ascii_figure='?' }, { .code = 0b01011, .ascii_letter='G', .ascii_figure='*' }, { .code = 0b00111, .ascii_letter='M', .ascii_figure='.' }, { .code = 0b10111, .ascii_letter='X', .ascii_figure='/' }, { .code = 0b01111, .ascii_letter='V', .ascii_figure='=' } }; const int n_rtty_baudot_items = sizeof(rtty_baudot_items) / sizeof(rtty_baudot_item_t); char rtty_baudot_decoder_lookup(unsigned char* fig_mode, unsigned char c) { if(c==RTTY_FIGURE_MODE_SELECT_CODE) { *fig_mode=1; return 0; } if(c==RTTY_LETTER_MODE_SELECT_CODE) { *fig_mode=0; return 0; } for(int i=0;istate) { case RTTY_BAUDOT_WAITING_STOP_PULSE: if(symbol==1) { s->state = RTTY_BAUDOT_WAITING_START_PULSE; if(s->character_received) return rtty_baudot_decoder_lookup(&s->fig_mode, s->shr&31); } //If the character data is followed by a stop pulse, then we go on to wait for the next character. else s->character_received = 0; //The character should be followed by a stop pulse. If the stop pulse is missing, that is certainly an error. //In that case, we remove forget the character we just received. break; case RTTY_BAUDOT_WAITING_START_PULSE: s->character_received = 0; if(symbol==0) { s->state = RTTY_BAUDOT_RECEIVING_DATA; s->shr = s->bit_cntr = 0; } //Any number of high bits can come after each other, until interrupted with a low bit (start pulse) to indicate //the beginning of a new character. If we get this start pulse, we go on to wait for the characters. We also //clear the variables used for counting (bit_cntr) and storing (shr) the data bits. break; case RTTY_BAUDOT_RECEIVING_DATA: s->shr = (s->shr<<1)|(!!symbol); //We store 5 bits into our shift register if(s->bit_cntr++==4) { s->state = RTTY_BAUDOT_WAITING_STOP_PULSE; s->character_received = 1; } //If this is the 5th bit stored, then we wait for the stop pulse. break; default: break; } return 0; } #define DEBUG_SERIAL_LINE_DECODER 0 //What has not been checked: // behaviour on 1.5 stop bits // check all exit conditions void serial_line_decoder_f_u8(serial_line_t* s, float* input, unsigned char* output, int input_size) { static int abs_samples_helper = 0; abs_samples_helper += s->input_used; int iabs_samples_helper = abs_samples_helper; s->output_size = 0; s->input_used = 0; short* output_s = (short*)output; unsigned* output_u = (unsigned*)output; for(;;) { //we find the start bit (first negative edge on the line) int startbit_start = -1; int i; for(i=1;i 0) { startbit_start=i; break; } if(startbit_start == -1) { s->input_used += i; DEBUG_SERIAL_LINE_DECODER && fprintf(stderr,"sld:startbit_not_found (+%d)\n", s->input_used); return; } DEBUG_SERIAL_LINE_DECODER && fprintf(stderr,"sld:startbit_found at %d (%d)\n", startbit_start, iabs_samples_helper + startbit_start); //If the stop bit would be too far so that we reached the end of the buffer, then we return failed. //The caller can rearrange the buffer so that the whole character fits into it. float all_bits = 1 + s->databits + s->stopbits; DEBUG_SERIAL_LINE_DECODER && fprintf(stderr,"sld:all_bits = %f\n", all_bits); if(startbit_start + s->samples_per_bits * all_bits >= input_size) { s->input_used += MAX_M(0,startbit_start-2); DEBUG_SERIAL_LINE_DECODER && fprintf(stderr,"sld:return_stopbit_too_far (+%d)\n", s->input_used); return; } //We do the actual sampling. int di; //databit counter unsigned shr = 0; for(di=0; di < s->databits; di++) { int databit_start = startbit_start + (1+di+(0.5*(1-s->bit_sampling_width_ratio))) * s->samples_per_bits; int databit_end = startbit_start + (1+di+(0.5*(1+s->bit_sampling_width_ratio))) * s->samples_per_bits; DEBUG_SERIAL_LINE_DECODER && fprintf(stderr,"sld:databit_start = %d (%d)\n", databit_start, iabs_samples_helper+databit_start); DEBUG_SERIAL_LINE_DECODER && fprintf(stderr,"sld:databit_end = %d (%d)\n", databit_end, iabs_samples_helper+databit_end); float databit_acc = 0; for(i=databit_start;i0)); shr=(shr<<1)|!!(databit_acc>0); } DEBUG_SERIAL_LINE_DECODER && fprintf(stderr,"sld:shr = 0x%x, %d\n", shr, shr); //We check if the stopbit is correct. int stopbit_start = startbit_start + (1+s->databits) * s->samples_per_bits + (s->stopbits * 0.5 * (1-s->bit_sampling_width_ratio)) * s->samples_per_bits; int stopbit_end = startbit_start + (1+s->databits) * s->samples_per_bits + (s->stopbits * 0.5 * (1+s->bit_sampling_width_ratio)) * s->samples_per_bits; DEBUG_SERIAL_LINE_DECODER && fprintf(stderr,"sld:stopbit_start = %d (%d)\n", stopbit_start, iabs_samples_helper+stopbit_start); DEBUG_SERIAL_LINE_DECODER && fprintf(stderr,"sld:stopbit_end = %d (%d)\n", stopbit_end, iabs_samples_helper+stopbit_end); float stopbit_acc = 0; for(i=stopbit_start;iinput_used += MIN_M(startbit_start + 1, input_size); DEBUG_SERIAL_LINE_DECODER && fprintf(stderr,"sld:return_stopbit_faulty (+%d)\n", s->input_used); return; } DEBUG_SERIAL_LINE_DECODER && fprintf(stderr,"sld:stopbit_found\n"); //we write the output sample if(s->databits <= 8) output[s->output_size] = shr; else if(s->databits <= 16) output_s[s->output_size] = shr; else output_u[s->output_size] = shr; s->output_size++; int samples_used_up_now = MIN_M(startbit_start + all_bits * s->samples_per_bits, input_size); s->input_used += samples_used_up_now; input += samples_used_up_now; input_size -= samples_used_up_now; iabs_samples_helper += samples_used_up_now; if(!input_size) { DEBUG_SERIAL_LINE_DECODER && fprintf(stderr,"sld:return_no_more_input (+%d)\n", s->input_used); return; } } DEBUG_SERIAL_LINE_DECODER && fprintf(stderr, "sld: >> output_size = %d (+%d)\n", s->output_size, s->input_used); } void generic_slicer_f_u8(float* input, unsigned char* output, int input_size, int n_symbols) { float symbol_distance = 2.0/(n_symbols-1); for(int i=0;i=symbol_low_limit) { output[i]=j; break; } } else { if(input[i]>=symbol_low_limit && input[i] 0; } void psk_modulator_u8_c(unsigned char* input, complexf* output, int input_size, int n_psk) { //outputs one complex sample per input symbol float phase_increment = (2*M_PI)/n_psk; for(int i=0;i>bi)&1; } unsigned char pack_bits_8to1_u8_u8(unsigned char* input) { unsigned char output; for(int i=0;i<8;i++) { output<<=1; output|=!!input[i]; } return output; } unsigned char differential_codec(unsigned char* input, unsigned char* output, int input_size, int encode, unsigned char state) { if(!encode) for(int i=0;i < | | | | | | | | | | | | | (_| | | | \ \ __/ (_| (_) \ V / __/ | | |_| | \_____\__,_|_| |_| |_|\___|_| \___/\/ |_| |_|_| |_| |_|_|_| |_|\__, | |_| \_\___|\___\___/ \_/ \___|_| \__, | __/ | __/ | |___/ |___/ */ void pll_cc_init_pi_controller(pll_t* p, float bandwidth, float ko, float kd, float damping_factor) { //kd: detector gain //ko: VCO gain float bandwidth_omega = 2*M_PI*bandwidth; p->alpha = (damping_factor*2*bandwidth_omega)/(ko*kd); float sampling_rate = 1; //the bandwidth is normalized to the sampling rate p->beta = (bandwidth_omega*bandwidth_omega)/(sampling_rate*ko*kd); p->iir_temp = p->dphase = p->output_phase = 0; } void pll_cc_init_p_controller(pll_t* p, float alpha) { p->alpha = alpha; p->dphase=p->output_phase=0; } void pll_cc(pll_t* p, complexf* input, float* output_dphase, complexf* output_nco, int input_size) { for(int i=0;ioutput_phase += p->dphase; while(p->output_phase>PI) p->output_phase-=2*PI; while(p->output_phase<-PI) p->output_phase+=2*PI; complexf current_nco; iof(¤t_nco,0) = sin(p->output_phase); qof(¤t_nco,0) = cos(p->output_phase); if(output_nco) output_nco[i] = current_nco; //we don't output anything if it is a NULL pointer //accurate phase detector: calculating error from phase offset float input_phase = atan2(iof(input,i),qof(input,i)); float new_dphase = input_phase - p->output_phase; while(new_dphase>PI) new_dphase-=2*PI; while(new_dphase<-PI) new_dphase+=2*PI; //modeling analog phase detector, which would be abs(input[i] * current_nco) if we had a real output signal, but what if we have complex signals? //qof(¤t_nco,0)=-qof(¤t_nco,0); //calculate conjugate //complexf multiply_result; //cmult(&multiply_result, &input[i], ¤t_nco); //output_nco[i] = multiply_result; //float new_dphase = absof(&multiply_result,0); if(p->pll_type == PLL_PI_CONTROLLER) { p->dphase = new_dphase * p->alpha + p->iir_temp; p->iir_temp += new_dphase * p->beta; while(p->dphase>PI) p->dphase-=2*PI; //won't need this one while(p->dphase<-PI) p->dphase+=2*PI; } else if(p->pll_type == PLL_P_CONTROLLER) { p->dphase = new_dphase * p->alpha; } else return; if(output_dphase) output_dphase[i] = -p->dphase; //if(output_dphase) output_dphase[i] = new_dphase/10; } } void octave_plot_point_on_cplxsig(complexf* signal, int signal_size, float error, int index, int correction_offset, char* writefiles_path, int points_size, ...) { static int figure_output_counter = 0; int* points_z = (int*)malloc(sizeof(int)*points_size); int* points_color = (int*)malloc(sizeof(int)*points_size); va_list vl; va_start(vl,points_size); for(int i=0;i=0 to_return.last_correction_offset = 0; to_return.earlylate_ratio = 0.25; //0..0.5 to_return.debug_writefiles_path = debug_writefiles_path; return to_return; } #define MTIMINGR_HDEBUG 0 void timing_recovery_cc(complexf* input, complexf* output, int input_size, float* timing_error, int* sampled_indexes, timing_recovery_state_t* state) { //We always assume that the input starts at center of the first symbol cross before the first symbol. //Last time we consumed that much from the input samples that it is there. int correction_offset = state->last_correction_offset; int current_bitstart_index = 0; int num_samples_bit = state->decimation_rate; int num_samples_halfbit = state->decimation_rate / 2; int num_samples_quarterbit = state->decimation_rate / 4; int num_samples_earlylate_wing = num_samples_bit * state->earlylate_ratio; float error; int el_point_left_index, el_point_right_index, el_point_mid_index; int si = 0; if(state->debug_every_nth>=0) fprintf(stderr, "disp(\"begin timing_recovery_cc\");\n"); if(MTIMINGR_HDEBUG) fprintf(stderr, "timing_recovery_cc started, nsb = %d, nshb = %d, nsqb = %d\n", num_samples_bit, num_samples_halfbit, num_samples_quarterbit); { for(;;) { //the MathWorks style algorithm has correction_offset. //correction_offset = 0; if(current_bitstart_index + num_samples_halfbit * 3 >= input_size) break; if(MTIMINGR_HDEBUG) fprintf(stderr, "current_bitstart_index = %d, input_size = %d, correction_offset(prev) = %d\n", current_bitstart_index, input_size, correction_offset); if(correction_offset<=-num_samples_quarterbit*0.9 || correction_offset>=0.9*num_samples_quarterbit) { if(MTIMINGR_HDEBUG) fprintf(stderr, "correction_offset = %d, reset to 0!\n", correction_offset); correction_offset = 0; } //should check if the sign of the correction_offset (or disabling it) has an effect on the EVM. //it is also a possibility to disable multiplying with the magnitude if(state->algorithm == TIMING_RECOVERY_ALGORITHM_EARLYLATE) { //bitstart index should be at symbol edge, maximum effect point is at current_bitstart_index + num_samples_halfbit el_point_right_index = current_bitstart_index + num_samples_earlylate_wing * 3; el_point_left_index = current_bitstart_index + num_samples_earlylate_wing * 1 - correction_offset; el_point_mid_index = current_bitstart_index + num_samples_halfbit; if(sampled_indexes) sampled_indexes[si]=el_point_mid_index; output[si++] = input[el_point_mid_index]; } else if(state->algorithm == TIMING_RECOVERY_ALGORITHM_GARDNER) { //maximum effect point is at current_bitstart_index el_point_right_index = current_bitstart_index + num_samples_halfbit * 3; el_point_left_index = current_bitstart_index + num_samples_halfbit * 1; el_point_mid_index = current_bitstart_index + num_samples_halfbit * 2; if(sampled_indexes) sampled_indexes[si]=el_point_left_index; output[si++] = input[el_point_left_index]; } else break; error = ( iof(input, el_point_right_index) - iof(input, el_point_left_index) ) * iof(input, el_point_mid_index); if(state->use_q) { error += ( qof(input, el_point_right_index) - qof(input, el_point_left_index)) * qof(input, el_point_mid_index); error /= 2; } //Original correction method: this version can only move a single sample in any direction //current_bitstart_index += num_samples_halfbit * 2 + (error)?((error<0)?1:-1):0; if(timing_error) timing_error[si-1]=error; //it is not written if NULL if(error>state->max_error) error=state->max_error; if(error<-state->max_error) error=-state->max_error; if(state->debug_every_nth>=0) { if(state->debug_every_nth==0 || state->debug_phase==0) { state->debug_phase = state->debug_every_nth; octave_plot_point_on_cplxsig(input+current_bitstart_index, state->decimation_rate*2, error, current_bitstart_index, correction_offset, state->debug_writefiles_path, 3, el_point_left_index - current_bitstart_index, 'r', el_point_right_index - current_bitstart_index, 'r', el_point_mid_index - current_bitstart_index, 'r', 0); } else state->debug_phase--; } int error_sign = (state->algorithm == TIMING_RECOVERY_ALGORITHM_GARDNER) ? -1 : 1; correction_offset = num_samples_halfbit * error_sign * error * state->loop_gain; current_bitstart_index += num_samples_bit + correction_offset; if(si>=input_size) { if(MTIMINGR_HDEBUG) fprintf(stderr, "oops_out_of_si!\n"); break; } } } state->input_processed = current_bitstart_index; state->output_size = si; state->last_correction_offset = correction_offset; } #define MTIMINGR_GAS(NAME) \ if(!strcmp( #NAME , input )) return TIMING_RECOVERY_ALGORITHM_ ## NAME; timing_recovery_algorithm_t timing_recovery_get_algorithm_from_string(char* input) { MTIMINGR_GAS(GARDNER); MTIMINGR_GAS(EARLYLATE); return TIMING_RECOVERY_ALGORITHM_DEFAULT; } #define MTIMINGR_GSA(NAME) \ if(algorithm == TIMING_RECOVERY_ALGORITHM_ ## NAME) return #NAME; char* timing_recovery_get_string_from_algorithm(timing_recovery_algorithm_t algorithm) { MTIMINGR_GSA(GARDNER); MTIMINGR_GSA(EARLYLATE); return "INVALID"; } void init_bpsk_costas_loop_cc(bpsk_costas_loop_state_t* s, int decision_directed, float damping_factor, float bandwidth) { //fprintf(stderr, "init_bpsk_costas_loop_cc: bandwidth = %f, damping_factor = %f\n", bandwidth, damping_factor); //based on: http://gnuradio.squarespace.com/blog/2011/8/13/control-loop-gain-values.html float bandwidth_omega = 2*PI*bandwidth; //so that the bandwidth should be around 0.01 by default (2pi/100), and the damping_factor should be default 0.707 float denomiator = 1+2*damping_factor*bandwidth_omega+bandwidth_omega*bandwidth_omega; fprintf(stderr, "damp = %f, bw = %f, bwomega = %f\n", damping_factor, bandwidth, bandwidth_omega); s->alpha = (4*damping_factor*bandwidth_omega)/denomiator; s->beta = (4*bandwidth_omega*bandwidth_omega)/denomiator; s->current_freq = s->dphase = s->nco_phase = 0; s->dphase_max=bandwidth_omega; //this has been determined by experiment: if dphase is out of [-dphase_max; dphase_max] it might actually hang and not come back s->dphase_max_reset_to_zero=0; } void bpsk_costas_loop_cc(complexf* input, complexf* output, int input_size, float* output_error, float* output_dphase, complexf* output_nco, bpsk_costas_loop_state_t* s) { for(int i=0;inco_phase); cmult(&output[i], &input[i], &nco_sample); if(output_nco) output_nco[i]=nco_sample; float error = 0; if(s->decision_directed) { float output_phase = atan2(qof(output,i),iof(output,i)); if (fabs(output_phase)PI) error -= 2*PI; } } else error = PI*iof(output,i)*qof(output,i); if(output_error) output_error[i]=error; s->current_freq += error * s->beta; s->dphase = error * s->alpha + s->current_freq; if(s->dphase>s->dphase_max) s->dphase = (s->dphase_max_reset_to_zero) ? 0 : s->dphase_max; if(s->dphase<-s->dphase_max) s->dphase = (s->dphase_max_reset_to_zero) ? 0 : -s->dphase_max; if(output_dphase) output_dphase[i]=s->dphase; //fprintf(stderr, " error = %f; dphase = %f; nco_phase = %f;\n", error, s->dphase, s->nco_phase); //step NCO s->nco_phase += s->dphase; while(s->nco_phase>2*PI) s->nco_phase-=2*PI; while(s->nco_phase<=0) s->nco_phase+=2*PI; } } #if 0 bpsk_costas_loop_state_t init_bpsk_costas_loop_cc(float samples_per_bits) { bpsk_costas_loop_state_t state; state.vco_phase = 0; state.last_vco_phase_addition = 0; float virtual_sampling_rate = 10000; float virtual_data_rate = virtual_sampling_rate / samples_per_bits; fprintf(stderr, "virtual_sampling_rate = %g, virtual_data_rate = %g\n", virtual_sampling_rate, virtual_data_rate); float rc_filter_cutoff = virtual_data_rate * 2; //this is so far the best float rc_filter_rc = 1/(2*M_PI*rc_filter_cutoff); //as of Equation 24 in Feigin float virtual_sampling_dt = 1.0/virtual_sampling_rate; fprintf(stderr, "rc_filter_cutoff = %g, rc_filter_rc = %g, virtual_sampling_dt = %g\n", rc_filter_cutoff, rc_filter_rc, virtual_sampling_dt); state.rc_filter_alpha = virtual_sampling_dt/(rc_filter_rc+virtual_sampling_dt); //https://en.wikipedia.org/wiki/Low-pass_filter float rc_filter_omega_cutoff = 2*M_PI*rc_filter_cutoff; state.vco_phase_addition_multiplier = 8*rc_filter_omega_cutoff / (virtual_sampling_rate); //as of Equation 25 in Feigin, assuming input signal amplitude of 1 (to 1V) and (state.vco_phase_addition_multiplier*), a value in radians, will be added to the vco_phase directly. fprintf(stderr, "rc_filter_alpha = %g, rc_filter_omega_cutoff = %g, vco_phase_addition_multiplier = %g\n", state.rc_filter_alpha, rc_filter_omega_cutoff, state.vco_phase_addition_multiplier); return state; } void bpsk_costas_loop_c1mc(complexf* input, complexf* output, int input_size, bpsk_costas_loop_state_t* state) { int debug = 0; if(debug) fprintf(stderr, "costas:\n"); for(int i=0;ivco_phase; if(debug) fprintf(stderr, "%g | %g\n", input_and_vco_mixed_phase, input_phase), debug--; complexf input_and_vco_mixed_sample; e_powj(&input_and_vco_mixed_sample, input_and_vco_mixed_phase); complexf vco_sample; e_powj(&vco_sample, -state->vco_phase); //cmult(&input_and_vco_mixed_sample, &input[i], &vco_sample);//if this is enabled, the real input sample is used, not the amplitude normalized float loop_output_i = input_and_vco_mixed_sample.i * state->rc_filter_alpha + state->last_lpfi_output * (1-state->rc_filter_alpha); float loop_output_q = input_and_vco_mixed_sample.q * state->rc_filter_alpha + state->last_lpfq_output * (1-state->rc_filter_alpha); //loop_output_i = input_and_vco_mixed_sample.i; //loop_output_q = input_and_vco_mixed_sample.q; state->last_lpfi_output = loop_output_i; state->last_lpfq_output = loop_output_q; float vco_phase_addition = loop_output_i * loop_output_q * state->vco_phase_addition_multiplier; //vco_phase_addition = vco_phase_addition * state->rc_filter_alpha + state->last_vco_phase_addition * (1-state->rc_filter_alpha); //state->last_vco_phase_addition = vco_phase_addition; state->vco_phase += vco_phase_addition; while(state->vco_phase>PI) state->vco_phase-=2*PI; while(state->vco_phase<-PI) state->vco_phase+=2*PI; cmult(&output[i], &input[i], &vco_sample); } } #endif void simple_agc_cc(complexf* input, complexf* output, int input_size, float rate, float reference, float max_gain, float* current_gain) { float rate_1minus=1-rate; int debugn = 0; for(int i=0;imax_gain) ideal_gain = max_gain; if(ideal_gain<=0) ideal_gain = 0; //*current_gain += (ideal_gain-(*current_gain))*rate; *current_gain = (ideal_gain-(*current_gain))*rate + (*current_gain)*rate_1minus; //if(debugn<100) fprintf(stderr, "cgain: %g\n", *current_gain), debugn++; output[i].i=(*current_gain)*input[i].i; output[i].q=(*current_gain)*input[i].q; } } void firdes_add_peak_c(complexf* output, int length, float rate, window_t window, int add, int normalize) { //add=0: malloc output previously //add=1: calloc output previously complexf* taps = (complexf*)malloc(sizeof(complexf)*length); int middle=length/2; float phase = 0, phase_addition = -rate*M_PI*2; float (*window_function)(float) = firdes_get_window_kernel(window); for(int i=0; i2*M_PI) phase-=2*M_PI; while(phase<0) phase+=2*M_PI; } //Normalize filter kernel if(add) for(int i=0;isamples_per_symbol/2) sinearest++; unsigned socorrect = initial_sample_offset+(sinearest*samples_per_symbol); //the sample offset which input[i] should have been, in order to sample at the maximum effect point int sodiff = abs(socorrect-input[i]); float ndiff = (float)sodiff/samples_per_symbol; ndiff_rad[i] = ndiff*PI; ndiff_rad_mean = ndiff_rad_mean*(((float)i)/(i+1))+(ndiff_rad[i]/(i+1)); if(debug_print) fprintf(stderr, "input[%d] = %u, sinearest = %u, socorrect = %u, sodiff = %u, ndiff = %f, ndiff_rad[i] = %f, ndiff_rad_mean = %f\n", i, input[i], sinearest, socorrect, sodiff, ndiff, ndiff_rad[i], ndiff_rad_mean); } fprintf(stderr, "ndiff_rad_mean = %f\n", ndiff_rad_mean); float result = 0; for(int i=0;i=PI) dphase-=2*PI; if( (dphase>(PI/2)) || (dphase<(-PI/2)) ) output[i]=0; else output[i]=1; last_input = input[i]; } } int bfsk_demod_cf(complexf* input, float* output, int input_size, complexf* mark_filter, complexf* space_filter, int taps_length) { complexf acc_space, acc_mark; for(int i=0; i 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