/* 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< { .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 }, //\ { .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; } 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 binary_slicer_f_u8(float* input, unsigned char* output, int input_size) { for(int i=0;i 0; } void pll_cc_init_2nd_order_IIR(pll_t* p, float bandwidth, float gain, float dampling_factor) { float k=0.9; p->filter_taps_a[0] = k*1; p->filter_taps_a[1] = k*(-2); p->filter_taps_a[2] = k*1; float tau1 = gain / (bandwidth*bandwidth); float tau2 = (2*dampling_factor) / bandwidth; p->filter_taps_b[0] = 4*(gain/tau1)*(1+tau2/2); p->filter_taps_b[1] = 8*(gain/tau1); p->filter_taps_b[2] = 4*(gain/tau1)*(1-tau2/2); p->last_filter_outputs[0]=p->last_filter_outputs[1]=p->last_filter_inputs[0]=p->last_filter_inputs[1]=0; p->dphase=p->output_phase=0; p->filter_taps_b[0] = 0.02868000; p->filter_taps_b[1] = 0.00080000; p->filter_taps_b[2] = -0.02788000; // s=tf([0.02868000,0.00080000,-0.02788000],[1 -2 1]); pzmap(s) } void pll_cc_init_1st_order_IIR(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_vco, 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; if(output_vco) //we don't output anything if it is a NULL pointer { iof(output_vco,i) = sin(p->output_phase); qof(output_vco,i) = cos(p->output_phase); } //ket komplex szam szorzataval inkabb float input_phase = atan2(iof(input,i),qof(input,i)); float new_dphase = input_phase - p->output_phase; //arg(input[i]/abs(input[i]) * conj(current_output_vco[i])) while(new_dphase>PI) new_dphase-=2*PI; while(new_dphase<-PI) new_dphase+=2*PI; if(p->pll_type == PLL_2ND_ORDER_IIR_LOOP_FILTER) { p->dphase = 0 //... + new_dphase * p->filter_taps_b[0] + p->last_filter_inputs[1] * p->filter_taps_b[1] + p->last_filter_inputs[0] * p->filter_taps_b[2] - p->last_filter_outputs[1] * p->filter_taps_a[1] - p->last_filter_outputs[0] * p->filter_taps_a[2]; //dphase /= filter_taps_a[0]; //The filter taps are already normalized, a[0]==1 always, so it is not necessary. p->last_filter_outputs[0]=p->last_filter_outputs[1]; p->last_filter_outputs[1]=p->dphase; p->last_filter_inputs[0]=p->last_filter_inputs[1]; p->last_filter_inputs[1]=new_dphase; while(p->dphase>PI) p->dphase-=2*PI; //ez nem fog kelleni while(p->dphase<-PI) p->dphase+=2*PI; } else if(p->pll_type == PLL_1ST_ORDER_IIR_LOOP_FILTER) { p->dphase = /*p->dphase * (1-p->alpha) +*/ new_dphase * p->alpha; } else return; if(output_dphase) output_dphase[i] = -p->dphase/10; // if(output_dphase) output_dphase[i] = new_dphase/10; } } /* _____ _ _ | __ \ | | (_) | | | | __ _| |_ __ _ ___ ___ _ ____ _____ _ __ ___ _ ___ _ __ | | | |/ _` | __/ _` | / __/ _ \| '_ \ \ / / _ \ '__/ __| |/ _ \| '_ \ | |__| | (_| | || (_| | | (_| (_) | | | \ V / __/ | \__ \ | (_) | | | | |_____/ \__,_|\__\__,_| \___\___/|_| |_|\_/ \___|_| |___/_|\___/|_| |_| */ void convert_u8_f(unsigned char* input, float* output, int input_size) { 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