mirror of
				https://github.com/f4exb/sdrangel.git
				synced 2025-10-31 04:50:29 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			1252 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1252 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*---------------------------------------------------------------------------*\
 | |
| 
 | |
|   FILE........: fsk.c
 | |
|   AUTHOR......: Brady O'Brien
 | |
|   DATE CREATED: 7 January 2016
 | |
| 
 | |
|   C Implementation of 2/4FSK modulator/demodulator, based on octave/fsk_horus.m
 | |
| 
 | |
| \*---------------------------------------------------------------------------*/
 | |
| 
 | |
| /*
 | |
|   Copyright (C) 2016 David Rowe
 | |
| 
 | |
|   All rights reserved.
 | |
| 
 | |
|   This program is free software; you can redistribute it and/or modify
 | |
|   it under the terms of the GNU Lesser General Public License version 2.1, as
 | |
|   published by the Free Software Foundation.  This program is
 | |
|   distributed in the hope that it will be useful, but WITHOUT ANY
 | |
|   WARRANTY; without even the implied warranty of MERCHANTABILITY or
 | |
|   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
 | |
|   License for more details.
 | |
| 
 | |
|   You should have received a copy of the GNU Lesser General Public License
 | |
|   along with this program; if not, see <http://www.gnu.org/licenses/>.
 | |
| */
 | |
| 
 | |
| /*---------------------------------------------------------------------------*\
 | |
| 
 | |
|                                DEFINES
 | |
| 
 | |
| \*---------------------------------------------------------------------------*/
 | |
| 
 | |
| /* P oversampling rate constant -- should probably be init-time configurable */
 | |
| #define horus_P 8
 | |
| 
 | |
| /* Define this to enable EbNodB estimate */
 | |
| /* This needs square roots, may take more cpu time than it's worth */
 | |
| #define EST_EBNO
 | |
| 
 | |
| /* This is a flag for the freq. estimator to use a precomputed/rt computed hann window table
 | |
|    On platforms with slow cosf, this will produce a substantial speedup at the cost of a small
 | |
|     amount of memory
 | |
| */
 | |
| #define USE_HANN_TABLE
 | |
| 
 | |
| /* This flag turns on run-time hann table generation. If USE_HANN_TABLE is unset,
 | |
|     this flag has no effect. If USE_HANN_TABLE is set and this flag is set, the
 | |
|     hann table will be allocated and generated when fsk_init or fsk_init_hbr is
 | |
|     called. If this flag is not set, a hann function table of size fsk->Ndft MUST
 | |
|     be provided. On small platforms, this can be used with a precomputed table to
 | |
|     save memory at the cost of flash space.
 | |
| */
 | |
| #define GENERATE_HANN_TABLE_RUNTIME
 | |
| 
 | |
| /* Turn off table generation if on cortex M4 to save memory */
 | |
| #ifdef CORTEX_M4
 | |
| #undef USE_HANN_TABLE
 | |
| #endif
 | |
| 
 | |
| /*---------------------------------------------------------------------------*\
 | |
| 
 | |
|                                INCLUDES
 | |
| 
 | |
| \*---------------------------------------------------------------------------*/
 | |
| 
 | |
| #include <assert.h>
 | |
| #include <stdlib.h>
 | |
| #include <stdint.h>
 | |
| #include <math.h>
 | |
| 
 | |
| #include "fsk.h"
 | |
| #include "comp_prim.h"
 | |
| #include "kiss_fftr.h"
 | |
| #include "modem_probe.h"
 | |
| 
 | |
| namespace FreeDV
 | |
| {
 | |
| 
 | |
| /*---------------------------------------------------------------------------*\
 | |
| 
 | |
|                                FUNCTIONS
 | |
| 
 | |
| \*---------------------------------------------------------------------------*/
 | |
| 
 | |
| static void stats_init(struct FSK *fsk);
 | |
| 
 | |
| #ifdef USE_HANN_TABLE
 | |
| /*
 | |
|    This is used by fsk_create and fsk_create_hbr to generate a hann function
 | |
|    table
 | |
| */
 | |
| static void fsk_generate_hann_table(struct FSK* fsk){
 | |
|     int Ndft = fsk->Ndft;
 | |
|     int i;
 | |
| 
 | |
|     /* Set up complex oscilator to calculate hann function */
 | |
|     COMP dphi = comp_exp_j((2*M_PI)/((float)Ndft-1));
 | |
|     COMP rphi = {.5,0};
 | |
| 
 | |
|     rphi = cmult(cconj(dphi),rphi);
 | |
| 
 | |
|     for (i = 0; i < Ndft; i++)
 | |
|     {
 | |
|         rphi = cmult(dphi,rphi);
 | |
|         float hannc = .5-rphi.real;
 | |
|         //float hann = .5-(.5*cosf((2*M_PI*(float)(i))/((float)Ndft-1)));
 | |
|         fsk->hann_table[i] = hannc;
 | |
|     }
 | |
| }
 | |
| #endif
 | |
| 
 | |
| 
 | |
| 
 | |
| /*---------------------------------------------------------------------------*\
 | |
| 
 | |
|   FUNCTION....: fsk_create_hbr
 | |
|   AUTHOR......: Brady O'Brien
 | |
|   DATE CREATED: 11 February 2016
 | |
| 
 | |
|   Create and initialize an instance of the FSK modem. Returns a pointer
 | |
|   to the modem state/config struct. One modem config struct may be used
 | |
|   for both mod and demod. returns NULL on failure.
 | |
| 
 | |
| \*---------------------------------------------------------------------------*/
 | |
| 
 | |
| struct FSK * fsk_create_hbr(int Fs, int Rs,int P,int M, int tx_f1, int tx_fs)
 | |
| {
 | |
|     struct FSK *fsk;
 | |
|     int i;
 | |
|     int memold;
 | |
|     int Ndft = 0;
 | |
|     /* Number of symbols in a processing frame */
 | |
|     int nsyms = 48;
 | |
|     /* Check configuration validity */
 | |
|     assert(Fs > 0 );
 | |
|     assert(Rs > 0 );
 | |
|     assert(tx_f1 > 0);
 | |
|     assert(tx_fs > 0);
 | |
|     assert(P > 0);
 | |
|     /* Ts (Fs/Rs) must be an integer */
 | |
|     assert( (Fs%Rs) == 0 );
 | |
|     /* Ts/P (Fs/Rs/P) must be an integer */
 | |
|     assert( ((Fs/Rs)%P) == 0 );
 | |
|     assert( M==2 || M==4);
 | |
| 
 | |
|     fsk = (struct FSK*) malloc(sizeof(struct FSK));
 | |
|     if(fsk == NULL) return NULL;
 | |
| 
 | |
| 
 | |
|     /* Set constant config parameters */
 | |
|     fsk->Fs = Fs;
 | |
|     fsk->Rs = Rs;
 | |
|     fsk->Ts = Fs/Rs;
 | |
|     fsk->burst_mode = 0;
 | |
|     fsk->N = fsk->Ts*nsyms;
 | |
|     fsk->P = P;
 | |
|     fsk->Nsym = nsyms;
 | |
|     fsk->Nmem = fsk->N+(2*fsk->Ts);
 | |
|     fsk->f1_tx = tx_f1;
 | |
|     fsk->fs_tx = tx_fs;
 | |
|     fsk->nin = fsk->N;
 | |
|     fsk->mode = M==2 ? MODE_2FSK : MODE_4FSK;
 | |
|     fsk->Nbits = M==2 ? fsk->Nsym : fsk->Nsym*2;
 | |
| 
 | |
|     /* Find smallest 2^N value that fits Fs for efficient FFT */
 | |
|     /* It would probably be better to use KISS-FFt's routine here */
 | |
|     for(i=1; i; i<<=1)
 | |
|         if((fsk->N)&i)
 | |
|             Ndft = i;
 | |
| 
 | |
|     fsk->Ndft = Ndft;
 | |
| 
 | |
|     fsk->est_min = Rs/4;
 | |
|     if(fsk->est_min<0) fsk->est_min = 0;
 | |
| 
 | |
|     fsk->est_max = (Fs/2)-Rs/4;
 | |
| 
 | |
|     fsk->est_space = Rs-(Rs/5);
 | |
| 
 | |
|     /* Set up rx state */
 | |
| 
 | |
|     for( i=0; i<M; i++)
 | |
|         fsk->phi_c[i] = comp_exp_j(0);
 | |
| 
 | |
|     memold = (4*fsk->Ts);
 | |
| 
 | |
|     fsk->nstash = memold;
 | |
|     fsk->samp_old = (COMP*) malloc(sizeof(COMP)*memold);
 | |
|     if(fsk->samp_old == NULL){
 | |
|         free(fsk);
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     for(i=0;i<memold;i++) {
 | |
|         fsk->samp_old[i].real = 0;
 | |
|         fsk->samp_old[i].imag = 0;
 | |
|     }
 | |
| 
 | |
|     fsk->fft_cfg = kiss_fft_alloc(fsk->Ndft,0,NULL,NULL);
 | |
|     if(fsk->fft_cfg == NULL){
 | |
|         free(fsk->samp_old);
 | |
|         free(fsk);
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     fsk->fft_est = (float*)malloc(sizeof(float)*fsk->Ndft/2);
 | |
|     if(fsk->fft_est == NULL){
 | |
|         free(fsk->samp_old);
 | |
|         free(fsk->fft_cfg);
 | |
|         free(fsk);
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     #ifdef USE_HANN_TABLE
 | |
|         #ifdef GENERATE_HANN_TABLE_RUNTIME
 | |
|             fsk->hann_table = (float*)malloc(sizeof(float)*fsk->Ndft);
 | |
|             if(fsk->hann_table == NULL){
 | |
|                 free(fsk->fft_est);
 | |
|                 free(fsk->samp_old);
 | |
|                 free(fsk->fft_cfg);
 | |
|                 free(fsk);
 | |
|                 return NULL;
 | |
|             }
 | |
|             fsk_generate_hann_table(fsk);
 | |
|         #else
 | |
|             fsk->hann_table = NULL;
 | |
|         #endif
 | |
|     #endif
 | |
| 
 | |
|     for(i=0;i<fsk->Ndft/2;i++)fsk->fft_est[i] = 0;
 | |
| 
 | |
|     fsk->norm_rx_timing = 0;
 | |
| 
 | |
|     /* Set up tx state */
 | |
|     fsk->tx_phase_c = comp_exp_j(0);
 | |
| 
 | |
|     /* Set up demod stats */
 | |
|     fsk->EbNodB = 0;
 | |
| 
 | |
|     for( i=0; i<M; i++)
 | |
|         fsk->f_est[i] = 0;
 | |
| 
 | |
|     fsk->ppm = 0;
 | |
| 
 | |
|     fsk->stats = (struct MODEM_STATS*)malloc(sizeof(struct MODEM_STATS));
 | |
|     if(fsk->stats == NULL){
 | |
|         free(fsk->fft_est);
 | |
|         free(fsk->samp_old);
 | |
|         free(fsk->fft_cfg);
 | |
|         free(fsk);
 | |
|         return NULL;
 | |
|     }
 | |
|     stats_init(fsk);
 | |
|     fsk->normalise_eye = 1;
 | |
| 
 | |
|     return fsk;
 | |
| }
 | |
| 
 | |
| 
 | |
| #define HORUS_MIN 800
 | |
| #define HORUS_MAX 2500
 | |
| #define HORUS_MIN_SPACING 100
 | |
| 
 | |
| /*---------------------------------------------------------------------------*\
 | |
| 
 | |
|   FUNCTION....: fsk_create
 | |
|   AUTHOR......: Brady O'Brien
 | |
|   DATE CREATED: 7 January 2016
 | |
| 
 | |
|   Create and initialize an instance of the FSK modem. Returns a pointer
 | |
|   to the modem state/config struct. One modem config struct may be used
 | |
|   for both mod and demod. returns NULL on failure.
 | |
| 
 | |
| \*---------------------------------------------------------------------------*/
 | |
| 
 | |
| struct FSK * fsk_create(int Fs, int Rs,int M, int tx_f1, int tx_fs)
 | |
| {
 | |
|     struct FSK *fsk;
 | |
|     int i;
 | |
|     int Ndft = 0;
 | |
|     int memold;
 | |
| 
 | |
|     /* Check configuration validity */
 | |
|     assert(Fs > 0 );
 | |
|     assert(Rs > 0 );
 | |
|     assert(tx_f1 > 0);
 | |
|     assert(tx_fs > 0);
 | |
|     assert(horus_P > 0);
 | |
|     /* Ts (Fs/Rs) must be an integer */
 | |
|     assert( (Fs%Rs) == 0 );
 | |
|     /* Ts/P (Fs/Rs/P) must be an integer */
 | |
|     assert( ((Fs/Rs)%horus_P) == 0 );
 | |
|     assert( M==2 || M==4);
 | |
| 
 | |
|     fsk = (struct FSK*) malloc(sizeof(struct FSK));
 | |
|     if(fsk == NULL) return NULL;
 | |
| 
 | |
|     Ndft = 1024;
 | |
| 
 | |
|     /* Set constant config parameters */
 | |
|     fsk->Fs = Fs;
 | |
|     fsk->Rs = Rs;
 | |
|     fsk->Ts = Fs/Rs;
 | |
|     fsk->N = Fs;
 | |
|     fsk->burst_mode = 0;
 | |
|     fsk->P = horus_P;
 | |
|     fsk->Nsym = fsk->N/fsk->Ts;
 | |
|     fsk->Ndft = Ndft;
 | |
|     fsk->Nmem = fsk->N+(2*fsk->Ts);
 | |
|     fsk->f1_tx = tx_f1;
 | |
|     fsk->fs_tx = tx_fs;
 | |
|     fsk->nin = fsk->N;
 | |
|     fsk->mode = M==2 ? MODE_2FSK : MODE_4FSK;
 | |
|     fsk->Nbits = M==2 ? fsk->Nsym : fsk->Nsym*2;
 | |
|     fsk->est_min = HORUS_MIN;
 | |
|     fsk->est_max = HORUS_MAX;
 | |
|     fsk->est_space = HORUS_MIN_SPACING;
 | |
| 
 | |
|     /* Set up rx state */
 | |
|     for( i=0; i<M; i++)
 | |
|         fsk->phi_c[i] = comp_exp_j(0);
 | |
| 
 | |
|     memold = (4*fsk->Ts);
 | |
| 
 | |
|     fsk->nstash = memold;
 | |
|     fsk->samp_old = (COMP*) malloc(sizeof(COMP)*memold);
 | |
|     if(fsk->samp_old == NULL){
 | |
|         free(fsk);
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     for(i=0;i<memold;i++) {
 | |
|         fsk->samp_old[i].real = 0.0;
 | |
|         fsk->samp_old[i].imag = 0.0;
 | |
|     }
 | |
| 
 | |
|     fsk->fft_cfg = kiss_fft_alloc(Ndft,0,NULL,NULL);
 | |
|     if(fsk->fft_cfg == NULL){
 | |
|         free(fsk->samp_old);
 | |
|         free(fsk);
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     fsk->fft_est = (float*)malloc(sizeof(float)*fsk->Ndft/2);
 | |
|     if(fsk->fft_est == NULL){
 | |
|         free(fsk->samp_old);
 | |
|         free(fsk->fft_cfg);
 | |
|         free(fsk);
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     #ifdef USE_HANN_TABLE
 | |
|         #ifdef GENERATE_HANN_TABLE_RUNTIME
 | |
|             fsk->hann_table = (float*)malloc(sizeof(float)*fsk->Ndft);
 | |
|             if(fsk->hann_table == NULL){
 | |
|                 free(fsk->fft_est);
 | |
|                 free(fsk->samp_old);
 | |
|                 free(fsk->fft_cfg);
 | |
|                 free(fsk);
 | |
|                 return NULL;
 | |
|             }
 | |
|             fsk_generate_hann_table(fsk);
 | |
|         #else
 | |
|             fsk->hann_table = NULL;
 | |
|         #endif
 | |
|     #endif
 | |
| 
 | |
|     for(i=0;i<Ndft/2;i++)fsk->fft_est[i] = 0;
 | |
| 
 | |
|     fsk->norm_rx_timing = 0;
 | |
| 
 | |
|     /* Set up tx state */
 | |
|     fsk->tx_phase_c = comp_exp_j(0);
 | |
| 
 | |
|     /* Set up demod stats */
 | |
|     fsk->EbNodB = 0;
 | |
| 
 | |
|     for( i=0; i<M; i++)
 | |
|         fsk->f_est[i] = 0;
 | |
| 
 | |
|     fsk->ppm = 0;
 | |
| 
 | |
|     fsk->stats = (struct MODEM_STATS*)malloc(sizeof(struct MODEM_STATS));
 | |
| 
 | |
|     if(fsk->stats == NULL){
 | |
|         free(fsk->fft_est);
 | |
|         free(fsk->samp_old);
 | |
|         free(fsk->fft_cfg);
 | |
|         free(fsk);
 | |
|         return NULL;
 | |
|     }
 | |
|     stats_init(fsk);
 | |
|     fsk->normalise_eye = 1;
 | |
| 
 | |
|     return fsk;
 | |
| }
 | |
| 
 | |
| /* make sure stats have known values in case monitoring process reads stats before they are set */
 | |
| 
 | |
| static void stats_init(struct FSK *fsk) {
 | |
|     /* Take a sample for the eye diagrams */
 | |
|     int i,j,m;
 | |
|     int P = fsk->P;
 | |
|     int M = fsk->mode;
 | |
| 
 | |
|     /* due to oversample rate P, we have too many samples for eye
 | |
|        trace.  So lets output a decimated version */
 | |
| 
 | |
|     /* asserts below as we found some problems over-running eye matrix */
 | |
| 
 | |
|     /* TODO: refactor eye tracing code here and in fsk_demod */
 | |
| 
 | |
|     int neyesamp_dec = ceil(((float)P*2)/MODEM_STATS_EYE_IND_MAX);
 | |
|     int neyesamp = (P*2)/neyesamp_dec;
 | |
|     assert(neyesamp <= MODEM_STATS_EYE_IND_MAX);
 | |
|     fsk->stats->neyesamp = neyesamp;
 | |
| 
 | |
|     int eye_traces = MODEM_STATS_ET_MAX/M;
 | |
| 
 | |
|     fsk->stats->neyetr = fsk->mode*eye_traces;
 | |
|     for(i=0; i<eye_traces; i++) {
 | |
|         for (m=0; m<M; m++){
 | |
|             for(j=0; j<neyesamp; j++) {
 | |
|                 assert((i*M+m) < MODEM_STATS_ET_MAX);
 | |
|                 fsk->stats->rx_eye[i*M+m][j] = 0;
 | |
|            }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fsk->stats->rx_timing = fsk->stats->snr_est = 0;
 | |
| 
 | |
| }
 | |
| 
 | |
| 
 | |
| void fsk_set_nsym(struct FSK *fsk,int nsyms){
 | |
|     assert(nsyms>0);
 | |
|     int Ndft,i;
 | |
|     Ndft = 0;
 | |
| 
 | |
|     /* Set constant config parameters */
 | |
|     fsk->N = fsk->Ts*nsyms;
 | |
|     fsk->Nsym = nsyms;
 | |
|     fsk->Nmem = fsk->N+(2*fsk->Ts);
 | |
|     fsk->nin = fsk->N;
 | |
|     fsk->Nbits = fsk->mode==2 ? fsk->Nsym : fsk->Nsym*2;
 | |
| 
 | |
|     /* Find smallest 2^N value that fits Fs for efficient FFT */
 | |
|     /* It would probably be better to use KISS-FFt's routine here */
 | |
|     for(i=1; i; i<<=1)
 | |
|         if((fsk->N)&i)
 | |
|             Ndft = i;
 | |
| 
 | |
|     fsk->Ndft = Ndft;
 | |
| 
 | |
|     free(fsk->fft_cfg);
 | |
|     free(fsk->fft_est);
 | |
| 
 | |
|     fsk->fft_cfg = kiss_fft_alloc(Ndft,0,NULL,NULL);
 | |
|     fsk->fft_est = (float*)malloc(sizeof(float)*fsk->Ndft/2);
 | |
| 
 | |
|     for(i=0;i<Ndft/2;i++)fsk->fft_est[i] = 0;
 | |
| 
 | |
| }
 | |
| 
 | |
| /* Set the FSK modem into burst demod mode */
 | |
| 
 | |
| void fsk_enable_burst_mode(struct FSK *fsk,int nsyms){
 | |
|     fsk_set_nsym(fsk,nsyms);
 | |
|     fsk->nin = fsk->N;
 | |
|     fsk->burst_mode = 1;
 | |
| }
 | |
| 
 | |
| void fsk_clear_estimators(struct FSK *fsk){
 | |
|     int i;
 | |
|     /* Clear freq estimator state */
 | |
|     for(i=0; i < (fsk->Ndft/2); i++){
 | |
|         fsk->fft_est[i] = 0;
 | |
|     }
 | |
|     /* Reset timing diff correction */
 | |
|     fsk->nin = fsk->N;
 | |
| }
 | |
| 
 | |
| uint32_t fsk_nin(struct FSK *fsk){
 | |
|     return (uint32_t)fsk->nin;
 | |
| }
 | |
| 
 | |
| void fsk_destroy(struct FSK *fsk){
 | |
|     free(fsk->fft_cfg);
 | |
|     free(fsk->samp_old);
 | |
|     free(fsk->stats);
 | |
|     free(fsk);
 | |
| }
 | |
| 
 | |
| void fsk_get_demod_stats(struct FSK *fsk, struct MODEM_STATS *stats){
 | |
|     /* copy from internal stats, note we can't overwrite stats completely
 | |
|        as it has other states rqd by caller, also we want a consistent
 | |
|        interface across modem types for the freedv_api.
 | |
|     */
 | |
| 
 | |
|     stats->clock_offset = fsk->stats->clock_offset;
 | |
|     stats->snr_est = fsk->stats->snr_est;           // TODO: make this SNR not Eb/No
 | |
|     stats->rx_timing = fsk->stats->rx_timing;
 | |
|     stats->foff = fsk->stats->foff;
 | |
| 
 | |
|     stats->neyesamp = fsk->stats->neyesamp;
 | |
|     stats->neyetr = fsk->stats->neyetr;
 | |
|     memcpy(stats->rx_eye, fsk->stats->rx_eye, sizeof(stats->rx_eye));
 | |
|     memcpy(stats->f_est, fsk->stats->f_est, fsk->mode*sizeof(float));
 | |
| 
 | |
|     /* these fields not used for FSK so set to something sensible */
 | |
| 
 | |
|     stats->sync = 0;
 | |
|     stats->nr = fsk->stats->nr;
 | |
|     stats->Nc = fsk->stats->Nc;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Set the minimum and maximum frequencies at which the freq. estimator can find tones
 | |
|  */
 | |
| void fsk_set_est_limits(struct FSK *fsk,int est_min, int est_max){
 | |
| 
 | |
|     fsk->est_min = est_min;
 | |
|     if(fsk->est_min<0) fsk->est_min = 0;
 | |
| 
 | |
|     fsk->est_max = est_max;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Internal function to estimate the frequencies of the two tones within a block of samples.
 | |
|  * This is split off because it is fairly complicated, needs a bunch of memory, and probably
 | |
|  * takes more cycles than the rest of the demod.
 | |
|  * Parameters:
 | |
|  * fsk - FSK struct from demod containing FSK config
 | |
|  * fsk_in - block of samples in this demod cycles, must be nin long
 | |
|  * freqs - Array for the estimated frequencies
 | |
|  * M - number of frequency peaks to find
 | |
|  */
 | |
| void fsk_demod_freq_est(struct FSK *fsk, COMP fsk_in[],float *freqs,int M){
 | |
|     int Ndft = fsk->Ndft;
 | |
|     int Fs = fsk->Fs;
 | |
|     int nin = fsk->nin;
 | |
|     int i,j;
 | |
|     float hann;
 | |
|     float max;
 | |
|     float tc;
 | |
|     int imax;
 | |
|     kiss_fft_cfg fft_cfg = fsk->fft_cfg;
 | |
|     int *freqi = new int[M];
 | |
|     int f_min,f_max,f_zero;
 | |
| 
 | |
|     /* Array to do complex FFT from using kiss_fft */
 | |
|     kiss_fft_cpx *fftin  = (kiss_fft_cpx*)malloc(sizeof(kiss_fft_cpx)*Ndft);
 | |
|     kiss_fft_cpx *fftout = (kiss_fft_cpx*)malloc(sizeof(kiss_fft_cpx)*Ndft);
 | |
| 
 | |
|     #ifndef USE_HANN_TABLE
 | |
|     COMP dphi = comp_exp_j((2*M_PI)/((float)Ndft-1));
 | |
|     COMP rphi = {.5,0};
 | |
|     rphi = cmult(cconj(dphi),rphi);
 | |
|     #endif
 | |
| 
 | |
|     f_min  = (fsk->est_min*Ndft)/Fs;
 | |
|     f_max  = (fsk->est_max*Ndft)/Fs;
 | |
|     f_zero = (fsk->est_space*Ndft)/Fs;
 | |
| 
 | |
|     /* scale averaging time constant based on number of samples */
 | |
|     tc = 0.95*Ndft/Fs;
 | |
| 
 | |
|     int samps;
 | |
|     int fft_samps;
 | |
|     int fft_loops = nin / Ndft;
 | |
| 
 | |
|     for (j = 0; j < fft_loops; j++)
 | |
|     {
 | |
|         /* 48000 sample rate (for example) will have a spare */
 | |
|         /* 896 samples besides the 46 "Ndft" samples, so adjust */
 | |
| 
 | |
|         samps = (nin - ((j + 1) * Ndft));
 | |
|         fft_samps = (samps >= Ndft) ? Ndft : samps;
 | |
| 
 | |
|         /* Copy FSK buffer into reals of FFT buffer and apply a hann window */
 | |
|         for(i=0; i<fft_samps; i++){
 | |
|             #ifdef USE_HANN_TABLE
 | |
|             hann = fsk->hann_table[i];
 | |
|             #else
 | |
|             //hann = 1-cosf((2*M_PI*(float)(i))/((float)fft_samps-1));
 | |
|             rphi = cmult(dphi,rphi);
 | |
|             hann = .5-rphi.real;
 | |
|             #endif
 | |
|             fftin[i].r = hann*fsk_in[i+Ndft*j].real;
 | |
|             fftin[i].i = hann*fsk_in[i+Ndft*j].imag;
 | |
|         }
 | |
| 
 | |
|         /* Zero out the remaining slots on spare samples */
 | |
|         for(; i<Ndft;i++){
 | |
|             fftin[i].r = 0;
 | |
|             fftin[i].i = 0;
 | |
|         }
 | |
| 
 | |
|         /* Do the FFT */
 | |
|         kiss_fft(fft_cfg,fftin,fftout);
 | |
| 
 | |
|         /* Find the magnitude^2 of each freq slot and stash away in the real
 | |
|         * value, so this only has to be done once. Since we're only comparing
 | |
|         * these values and only need the mag of 2 points, we don't need to do
 | |
|         * a sqrt to each value */
 | |
|         for(i=0; i<Ndft/2; i++){
 | |
|             fftout[i].r = (fftout[i].r*fftout[i].r) + (fftout[i].i*fftout[i].i) ;
 | |
|         }
 | |
| 
 | |
|         /* Zero out the minimum and maximum ends */
 | |
|         for(i=0; i<f_min; i++){
 | |
|             fftout[i].r = 0;
 | |
|         }
 | |
|         for(i=f_max-1; i<Ndft/2; i++){
 | |
|             fftout[i].r = 0;
 | |
|         }
 | |
|         /* Mix back in with the previous fft block */
 | |
|         /* Copy new fft est into imag of fftout for frequency divination below */
 | |
|         for(i=0; i<Ndft/2; i++){
 | |
|             fsk->fft_est[i] = (fsk->fft_est[i]*(1-tc)) + (sqrtf(fftout[i].r)*tc);
 | |
|             fftout[i].i = fsk->fft_est[i];
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     modem_probe_samp_f("t_fft_est", fsk->fft_est, Ndft/2);
 | |
| 
 | |
|     max = 0;
 | |
|     /* Find the M frequency peaks here */
 | |
|     for(i=0; i<M; i++){
 | |
|         imax = 0;
 | |
|         max = 0;
 | |
|         for(j=0;j<Ndft/2;j++){
 | |
|             if(fftout[j].i > max){
 | |
|                 max = fftout[j].i;
 | |
|                 imax = j;
 | |
|             }
 | |
|         }
 | |
|         /* Blank out FMax +/-Fspace/2 */
 | |
|         f_min = imax - f_zero;
 | |
|         f_min = f_min < 0 ? 0 : f_min;
 | |
|         f_max = imax + f_zero;
 | |
|         f_max = f_max > Ndft ? Ndft : f_max;
 | |
|         for(j=f_min; j<f_max; j++)
 | |
|             fftout[j].i = 0;
 | |
| 
 | |
|         /* Stick the freq index on the list */
 | |
|         freqi[i] = imax;
 | |
|     }
 | |
| 
 | |
|     /* Gnome sort the freq list */
 | |
|     /* My favorite sort of sort*/
 | |
|     i = 1;
 | |
|     while(i<M){
 | |
|         if(freqi[i] >= freqi[i-1]) i++;
 | |
|         else{
 | |
|             j = freqi[i];
 | |
|             freqi[i] = freqi[i-1];
 | |
|             freqi[i-1] = j;
 | |
|             if(i>1) i--;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /* Convert freqs from indices to frequencies */
 | |
|     for(i=0; i<M; i++){
 | |
|         freqs[i] = (float)(freqi[i])*((float)Fs/(float)Ndft);
 | |
|     }
 | |
| 
 | |
|     delete[] freqi;
 | |
|     free(fftin);
 | |
|     free(fftout);
 | |
| }
 | |
| 
 | |
| void fsk2_demod(struct FSK *fsk, uint8_t rx_bits[], float rx_sd[], COMP fsk_in[]){
 | |
|     int N = fsk->N;
 | |
|     int Ts = fsk->Ts;
 | |
|     int Rs = fsk->Rs;
 | |
|     int Fs = fsk->Fs;
 | |
|     int nsym = fsk->Nsym;
 | |
|     int nin = fsk->nin;
 | |
|     int P = fsk->P;
 | |
|     int Nmem = fsk->Nmem;
 | |
|     int M = fsk->mode;
 | |
|     int i, j, m, dc_i, cbuf_i;
 | |
|     float ft1;
 | |
|     int nstash = fsk->nstash;
 | |
| 
 | |
|     COMP* *f_int = new COMP*[M];    /* Filtered and downsampled symbol tones */
 | |
|     COMP *t = new COMP[M];          /* complex number temps */
 | |
|     COMP t_c;           /* another complex temp */
 | |
|     COMP *phi_c = new COMP[M];
 | |
|     COMP phi_ft;
 | |
|     int nold = Nmem-nin;
 | |
| 
 | |
|     COMP *dphi = new COMP[M];
 | |
|     COMP dphift;
 | |
|     float rx_timing,norm_rx_timing,old_norm_rx_timing,d_norm_rx_timing,appm;
 | |
|     int using_old_samps;
 | |
| 
 | |
|     COMP* sample_src;
 | |
|     COMP* f_intbuf_m;
 | |
| 
 | |
|     float *f_est = new float[M];
 | |
|     float fc_avg, fc_tx;
 | |
|     float meanebno,stdebno,eye_max;
 | |
|     int neyesamp,neyeoffset;
 | |
| 
 | |
|     #ifdef MODEMPROBE_ENABLE
 | |
|     char mp_name_tmp[20]; /* Temporary string for modem probe trace names */
 | |
|     #endif
 | |
| 
 | |
|     //for(size_t jj = 0; jj<nin; jj++){
 | |
|     //    fprintf(stderr,"%f,j%f,",fsk_in[jj].real,fsk_in[jj].imag);
 | |
|     //}
 | |
| 
 | |
|     /* Load up demod phases from struct */
 | |
|     for(m = 0; m < M; m++) {
 | |
|         phi_c[m] = fsk->phi_c[m];
 | |
|     }
 | |
| 
 | |
|     /* Estimate tone frequencies */
 | |
|     fsk_demod_freq_est(fsk,fsk_in,f_est,M);
 | |
|     modem_probe_samp_f("t_f_est",f_est,M);
 | |
| 
 | |
| 
 | |
|     /* Allocate circular buffer for integration */
 | |
|     f_intbuf_m = (COMP*) malloc(sizeof(COMP)*Ts);
 | |
| 
 | |
|     /* allocate memory for the integrated samples */
 | |
|     for( m=0; m<M; m++){
 | |
|         /* allocate memory for the integrated samples */
 | |
|         /* Note: This must be kept after fsk_demod_freq_est for memory usage reasons */
 | |
|         f_int[m] = (COMP*) malloc(sizeof(COMP)*(nsym+1)*P);
 | |
|     }
 | |
| 
 | |
|     /* If this is the first run, we won't have any valid f_est */
 | |
|     /* TODO: add first_run flag to FSK to make negative freqs possible */
 | |
|     if(fsk->f_est[0]<1){
 | |
|         for( m=0; m<M; m++)
 | |
|             fsk->f_est[m] = f_est[m];
 | |
|     }
 | |
| 
 | |
|     /* Initalize downmixers for each symbol tone */
 | |
|     for( m=0; m<M; m++){
 | |
|         /* Back the stored phase off to account for re-integraton of old samples */
 | |
|         dphi[m] = comp_exp_j(-2*(Nmem-nin-(Ts/P))*M_PI*((fsk->f_est[m])/(float)(Fs)));
 | |
|         phi_c[m] = cmult(dphi[m],phi_c[m]);
 | |
|         //fprintf(stderr,"F%d = %f",m,fsk->f_est[m]);
 | |
| 
 | |
|         /* Figure out how much to nudge each sample downmixer for every sample */
 | |
|         dphi[m] = comp_exp_j(2*M_PI*((fsk->f_est[m])/(float)(Fs)));
 | |
|     }
 | |
| 
 | |
|     /* Integrate and downsample for symbol tones */
 | |
|     for(m=0; m<M; m++){
 | |
|         /* Copy buffer pointers in to avoid second buffer indirection */
 | |
|         float f_est_m = f_est[m];
 | |
|         COMP* f_int_m = &(f_int[m][0]);
 | |
|         COMP dphi_m = dphi[m];
 | |
| 
 | |
|         dc_i = 0;
 | |
|         cbuf_i = 0;
 | |
|         sample_src = &(fsk->samp_old[nstash-nold]);
 | |
|         using_old_samps = 1;
 | |
| 
 | |
|         /* Pre-fill integration buffer */
 | |
|         for(dc_i=0; dc_i<Ts-(Ts/P); dc_i++){
 | |
|             /* Switch sample source to new samples when we run out of old ones */
 | |
|             if(dc_i>=nold && using_old_samps){
 | |
|                 sample_src = &fsk_in[0];
 | |
|                 dc_i = 0;
 | |
|                 using_old_samps = 0;
 | |
| 
 | |
|                 /* Recalculate delta-phi after switching to new sample source */
 | |
|                 phi_c[m] = comp_normalize(phi_c[m]);
 | |
|                 dphi_m = comp_exp_j(2*M_PI*((f_est_m)/(float)(Fs)));
 | |
|             }
 | |
|             /* Downconvert and place into integration buffer */
 | |
|             f_intbuf_m[dc_i]=cmult(sample_src[dc_i],cconj(phi_c[m]));
 | |
| 
 | |
|             #ifdef MODEMPROBE_ENABLE
 | |
|             snprintf(mp_name_tmp,19,"t_f%zd_dc",m+1);
 | |
|             modem_probe_samp_c(mp_name_tmp,&f_intbuf_m[dc_i],1);
 | |
|             #endif
 | |
|             /* Spin downconversion phases */
 | |
|             phi_c[m] = cmult(phi_c[m],dphi_m);
 | |
|         }
 | |
|         cbuf_i = dc_i;
 | |
| 
 | |
|         /* Integrate over Ts at offsets of Ts/P */
 | |
|         for(i=0; i<(nsym+1)*P; i++){
 | |
|             /* Downconvert and Place Ts/P samples in the integration buffers */
 | |
|             for(j=0; j<(Ts/P); j++,dc_i++){
 | |
|                 /* Switch sample source to new samples when we run out of old ones */
 | |
|                 if(dc_i>=nold && using_old_samps){
 | |
|                     sample_src = &fsk_in[0];
 | |
|                     dc_i = 0;
 | |
|                     using_old_samps = 0;
 | |
| 
 | |
|                     /* Recalculate delta-phi after switching to new sample source */
 | |
|                     phi_c[m] = comp_normalize(phi_c[m]);
 | |
|                     dphi_m = comp_exp_j(2*M_PI*((f_est_m)/(float)(Fs)));
 | |
|                 }
 | |
|                 /* Downconvert and place into integration buffer */
 | |
|                 f_intbuf_m[cbuf_i+j]=cmult(sample_src[dc_i],cconj(phi_c[m]));
 | |
| 
 | |
|                 #ifdef MODEMPROBE_ENABLE
 | |
|                 snprintf(mp_name_tmp,19,"t_f%zd_dc",m+1);
 | |
|                 modem_probe_samp_c(mp_name_tmp,&f_intbuf_m[cbuf_i+j],1);
 | |
|                 #endif
 | |
|                 /* Spin downconversion phases */
 | |
|                 phi_c[m] = cmult(phi_c[m],dphi_m);
 | |
| 
 | |
|             }
 | |
| 
 | |
|             /* Dump internal samples */
 | |
|             cbuf_i += Ts/P;
 | |
|             if(cbuf_i>=Ts) cbuf_i = 0;
 | |
| 
 | |
|             /* Integrate over the integration buffers, save samples */
 | |
|             float it_r = 0;
 | |
|             float it_i = 0;
 | |
|             for(j=0; j<Ts; j++){
 | |
|                 it_r += f_intbuf_m[j].real;
 | |
|                 it_i += f_intbuf_m[j].imag;
 | |
|             }
 | |
|             f_int_m[i].real = it_r;
 | |
|             f_int_m[i].imag = it_i;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /* Save phases back into FSK struct */
 | |
|     for(m=0; m<M; m++){
 | |
|         fsk->phi_c[m] = phi_c[m];
 | |
|         fsk->f_est[m] = f_est[m];
 | |
|     }
 | |
| 
 | |
|     /* Stash samples away in the old sample buffer for the next round of bit getting */
 | |
|     memcpy((void*)&(fsk->samp_old[0]),(void*)&(fsk_in[nin-nstash]),sizeof(COMP)*nstash);
 | |
| 
 | |
|     /* Fine Timing Estimation */
 | |
|     /* Apply magic nonlinearity to f1_int and f2_int, shift down to 0,
 | |
|      * extract angle */
 | |
| 
 | |
|     /* Figure out how much to spin the oscillator to extract magic spectral line */
 | |
|     dphift = comp_exp_j(2*M_PI*((float)(Rs)/(float)(P*Rs)));
 | |
|     phi_ft.real = 1;
 | |
|     phi_ft.imag = 0;
 | |
|     t_c=comp0();
 | |
|     for(i=0; i<(nsym+1)*P; i++){
 | |
|         /* Get abs^2 of fx_int[i], and add 'em */
 | |
|         ft1 = 0;
 | |
|         for( m=0; m<M; m++){
 | |
|             ft1 += (f_int[m][i].real*f_int[m][i].real) + (f_int[m][i].imag*f_int[m][i].imag);
 | |
|         }
 | |
| 
 | |
|         /* Down shift and accumulate magic line */
 | |
|         t_c = cadd(t_c,fcmult(ft1,phi_ft));
 | |
| 
 | |
|         /* Spin the oscillator for the magic line shift */
 | |
|         phi_ft = cmult(phi_ft,dphift);
 | |
|     }
 | |
| 
 | |
|     /* Check for NaNs in the fine timing estimate, return if found */
 | |
|     /* otherwise segfaults happen */
 | |
|     if( isnan(t_c.real) || isnan(t_c.imag)){
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     /* Get the magic angle */
 | |
|     norm_rx_timing =  atan2f(t_c.imag,t_c.real)/(2*M_PI);
 | |
|     rx_timing = norm_rx_timing*(float)P;
 | |
| 
 | |
|     old_norm_rx_timing = fsk->norm_rx_timing;
 | |
|     fsk->norm_rx_timing = norm_rx_timing;
 | |
| 
 | |
|     /* Estimate sample clock offset */
 | |
|     d_norm_rx_timing = norm_rx_timing - old_norm_rx_timing;
 | |
| 
 | |
|     /* Filter out big jumps in due to nin change */
 | |
|     if(fabsf(d_norm_rx_timing) < .2){
 | |
|         appm = 1e6*d_norm_rx_timing/(float)nsym;
 | |
|         fsk->ppm = .9*fsk->ppm + .1*appm;
 | |
|     }
 | |
| 
 | |
|     /* Figure out how many samples are needed the next modem cycle */
 | |
|     /* Unless we're in burst mode */
 | |
|     if(!fsk->burst_mode){
 | |
|         if(norm_rx_timing > 0.25)
 | |
|             fsk->nin = N+Ts/2;
 | |
|         else if(norm_rx_timing < -0.25)
 | |
|             fsk->nin = N-Ts/2;
 | |
|         else
 | |
|             fsk->nin = N;
 | |
|     }
 | |
| 
 | |
|     modem_probe_samp_f("t_norm_rx_timing",&(norm_rx_timing),1);
 | |
|     modem_probe_samp_i("t_nin",&(fsk->nin),1);
 | |
| 
 | |
|     /* Re-sample the integrators with linear interpolation magic */
 | |
|     int low_sample = (int)floorf(rx_timing);
 | |
|     float fract = rx_timing - (float)low_sample;
 | |
|     int high_sample = (int)ceilf(rx_timing);
 | |
| 
 | |
|     /* Vars for finding the max-of-4 for each bit */
 | |
|     float *tmax = new float[M];
 | |
| 
 | |
|     #ifdef EST_EBNO
 | |
|     meanebno = 0;
 | |
|     stdebno = 0;
 | |
|     #endif
 | |
| 
 | |
|     /* FINALLY, THE BITS */
 | |
|     /* also, resample fx_int */
 | |
|     for(i = 0; i < nsym; i++)
 | |
|     {
 | |
|         int st = (i+1)*P;
 | |
|         for( m=0; m<M; m++){
 | |
|             t[m] =           fcmult(1-fract,f_int[m][st+ low_sample]);
 | |
|             t[m] = cadd(t[m],fcmult(  fract,f_int[m][st+high_sample]));
 | |
|             /* Figure mag^2 of each resampled fx_int */
 | |
|             tmax[m] = (t[m].real*t[m].real) + (t[m].imag*t[m].imag);
 | |
|         }
 | |
| 
 | |
|         float max = tmax[0]; /* Maximum for figuring correct symbol */
 | |
|         float min = tmax[0];
 | |
|         int sym = 0; /* Index of maximum */
 | |
|         for( m=0; m<M; m++){
 | |
|             if(tmax[m]>max){
 | |
|                 max = tmax[m];
 | |
|                 sym = m;
 | |
|             }
 | |
|             if(tmax[m]<min){
 | |
|                 min = tmax[m];
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /* Get the actual bit */
 | |
|         if(rx_bits != NULL){
 | |
|             /* Get bits for 2FSK and 4FSK */
 | |
|             /* TODO: Replace this with something more generic maybe */
 | |
|             if(M==2){
 | |
|                 rx_bits[i] = sym==1;                /* 2FSK. 1 bit per symbol */
 | |
|             }else if(M==4){
 | |
|                 rx_bits[(i*2)+1] = (sym&0x1);       /* 4FSK. 2 bits per symbol */
 | |
|                 rx_bits[(i*2)  ] = (sym&0x2)>>1;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /* Produce soft decision symbols */
 | |
|         if(rx_sd != NULL){
 | |
|             /* Convert symbols from max^2 into max */
 | |
|             for( m=0; m<M; m++)
 | |
|                 tmax[m] = sqrtf(tmax[m]);
 | |
| 
 | |
|             if(M==2){
 | |
|                 rx_sd[i] = tmax[0] - tmax[1];
 | |
|             }else if(M==4){
 | |
|                 /* TODO: Find a soft-decision mode that works for 4FSK */
 | |
|                 min = sqrtf(min);
 | |
|                 rx_sd[(i*2)+1] = - tmax[0] ;  /* Bits=00 */
 | |
|                 rx_sd[(i*2)  ] = - tmax[0] ;
 | |
|                 rx_sd[(i*2)+1]+=   tmax[1] ;  /* Bits=01 */
 | |
|                 rx_sd[(i*2)  ]+= - tmax[1] ;
 | |
|                 rx_sd[(i*2)+1]+= - tmax[2] ;  /* Bits=10 */
 | |
|                 rx_sd[(i*2)  ]+=   tmax[2] ;
 | |
|                 rx_sd[(i*2)+1]+=   tmax[3] ;  /* Bits=11 */
 | |
|                 rx_sd[(i*2)  ]+=   tmax[3] ;
 | |
|             }
 | |
|         }
 | |
|         /* Accumulate resampled int magnitude for EbNodB estimation */
 | |
|         /* Standard deviation is calculated by algorithm devised by crafty soviets */
 | |
|         #ifdef EST_EBNO
 | |
|         /* Accumulate the square of the sampled value */
 | |
|         ft1 = max;
 | |
|         stdebno += ft1;
 | |
| 
 | |
|         /* Figure the abs value of the max tone */
 | |
|         meanebno += sqrtf(ft1);
 | |
|         #endif
 | |
|         /* Soft output goes here */
 | |
|     }
 | |
| 
 | |
|     delete[] tmax;
 | |
| 
 | |
|     #ifdef EST_EBNO
 | |
| 
 | |
|     /* Calculate mean for EbNodB estimation */
 | |
|     meanebno = meanebno/(float)nsym;
 | |
| 
 | |
|     /* Calculate the std. dev for EbNodB estimate */
 | |
|     stdebno = (stdebno/(float)nsym) - (meanebno*meanebno);
 | |
|     /* trap any negative numbers to avoid NANs flowing through */
 | |
|     if (stdebno > 0.0) {
 | |
|         stdebno = sqrt(stdebno);
 | |
|     } else {
 | |
|         stdebno = 0.0;
 | |
|     }
 | |
| 
 | |
|     fsk->EbNodB = -6+(20*log10f((1e-6+meanebno)/(1e-6+stdebno)));
 | |
|     #else
 | |
|     fsk->EbNodB = 1;
 | |
|     #endif
 | |
| 
 | |
|     /* Write some statistics to the stats struct */
 | |
| 
 | |
|     /* Save clock offset in ppm */
 | |
|     fsk->stats->clock_offset = fsk->ppm;
 | |
| 
 | |
|     /* Calculate and save SNR from EbNodB estimate */
 | |
| 
 | |
|     fsk->stats->snr_est = .5*fsk->stats->snr_est + .5*fsk->EbNodB;//+ 10*log10f(((float)Rs)/((float)Rs*M));
 | |
| 
 | |
|     /* Save rx timing */
 | |
|     fsk->stats->rx_timing = (float)rx_timing;
 | |
| 
 | |
|     /* Estimate and save frequency offset */
 | |
|     fc_avg = (f_est[0]+f_est[1])/2;
 | |
|     fc_tx = (fsk->f1_tx+fsk->f1_tx+fsk->fs_tx)/2;
 | |
|     fsk->stats->foff = fc_tx-fc_avg;
 | |
| 
 | |
|     /* Take a sample for the eye diagrams ---------------------------------- */
 | |
| 
 | |
|     /* due to oversample rate P, we have too many samples for eye
 | |
|        trace.  So lets output a decimated version.  We use 2P
 | |
|        as we want two symbols worth of samples in trace  */
 | |
| 
 | |
|     int neyesamp_dec = ceil(((float)P*2)/MODEM_STATS_EYE_IND_MAX);
 | |
|     neyesamp = (P*2)/neyesamp_dec;
 | |
|     assert(neyesamp <= MODEM_STATS_EYE_IND_MAX);
 | |
|     fsk->stats->neyesamp = neyesamp;
 | |
| 
 | |
|     #ifdef I_DONT_UNDERSTAND
 | |
|     neyeoffset = high_sample+1+(P*28); /* WTF this line? Where does "28" come from ?                           */
 | |
|     #endif                             /* ifdef-ed out as I am afraid it will index out of memory as P changes */
 | |
|     neyeoffset = high_sample+1;
 | |
| 
 | |
|     int eye_traces = MODEM_STATS_ET_MAX/M;
 | |
|     int ind;
 | |
| 
 | |
|     fsk->stats->neyetr = fsk->mode*eye_traces;
 | |
|     for( i=0; i<eye_traces; i++){
 | |
|         for ( m=0; m<M; m++){
 | |
|             for(j=0; j<neyesamp; j++) {
 | |
|                /*
 | |
|                   2*P*i...........: advance two symbols for next trace
 | |
|                   neyeoffset......: centre trace on ideal timing offset, peak eye opening
 | |
|                   j*neweyesamp_dec: For 2*P>MODEM_STATS_EYE_IND_MAX advance through integrated
 | |
|                                     samples newamp_dec at a time so we dont overflow rx_eye[][]
 | |
|                */
 | |
|                ind = 2*P*i + neyeoffset + j*neyesamp_dec;
 | |
|                assert((i*M+m) < MODEM_STATS_ET_MAX);
 | |
|                assert(ind < (nsym+1)*P);
 | |
|                fsk->stats->rx_eye[i*M+m][j] = cabsolute(f_int[m][ind]);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (fsk->normalise_eye) {
 | |
|         eye_max = 0;
 | |
|         /* Normalize eye to +/- 1 */
 | |
|         for(i=0; i<M*eye_traces; i++)
 | |
|             for(j=0; j<neyesamp; j++)
 | |
|                 if(fabsf(fsk->stats->rx_eye[i][j])>eye_max)
 | |
|                     eye_max = fabsf(fsk->stats->rx_eye[i][j]);
 | |
| 
 | |
|         for(i=0; i<M*eye_traces; i++)
 | |
|             for(j=0; j<neyesamp; j++)
 | |
|                 fsk->stats->rx_eye[i][j] = fsk->stats->rx_eye[i][j]/eye_max;
 | |
|     }
 | |
| 
 | |
|     fsk->stats->nr = 0;
 | |
|     fsk->stats->Nc = 0;
 | |
| 
 | |
|     for(i=0; i<M; i++) {
 | |
|         fsk->stats->f_est[i] = f_est[i];
 | |
|     }
 | |
| 
 | |
|     /* Dump some internal samples */
 | |
|     modem_probe_samp_f("t_EbNodB",&(fsk->EbNodB),1);
 | |
|     modem_probe_samp_f("t_ppm",&(fsk->ppm),1);
 | |
|     modem_probe_samp_f("t_rx_timing",&(rx_timing),1);
 | |
| 
 | |
|     #ifdef MODEMPROBE_ENABLE
 | |
|     for( m=0; m<M; m++){
 | |
|         snprintf(mp_name_tmp,19,"t_f%zd_int",m+1);
 | |
|         modem_probe_samp_c(mp_name_tmp,f_int[m],(nsym+1)*P);
 | |
|         snprintf(mp_name_tmp,19,"t_f%zd",m+1);
 | |
|         modem_probe_samp_f(mp_name_tmp,&f_est[m],1);
 | |
|     }
 | |
|     #endif
 | |
| 
 | |
|     for( m=0; m<M; m++){
 | |
|         free(f_int[m]);
 | |
|     }
 | |
|     free(f_intbuf_m);
 | |
| 
 | |
|     delete[] f_est;
 | |
|     delete[] dphi;
 | |
|     delete[] phi_c;
 | |
|     delete[] t;
 | |
|     delete[] f_int;
 | |
| }
 | |
| 
 | |
| void fsk_demod(struct FSK *fsk, uint8_t rx_bits[], COMP fsk_in[]){
 | |
|     fsk2_demod(fsk,rx_bits,NULL,fsk_in);
 | |
| }
 | |
| 
 | |
| void fsk_demod_sd(struct FSK *fsk, float rx_sd[], COMP fsk_in[]){
 | |
|     fsk2_demod(fsk,NULL,rx_sd,fsk_in);
 | |
| }
 | |
| 
 | |
| void fsk_mod(struct FSK *fsk,float fsk_out[],uint8_t tx_bits[]){
 | |
|     COMP tx_phase_c = fsk->tx_phase_c; /* Current complex TX phase */
 | |
|     int f1_tx = fsk->f1_tx;         /* '0' frequency */
 | |
|     int fs_tx = fsk->fs_tx;         /* space between frequencies */
 | |
|     int Ts = fsk->Ts;               /* samples-per-symbol */
 | |
|     int Fs = fsk->Fs;               /* sample freq */
 | |
|     int M = fsk->mode;
 | |
|     COMP *dosc_f = new COMP[M];     /* phase shift per sample */
 | |
|     COMP dph;                       /* phase shift of current bit */
 | |
|     int i, j, m, bit_i, sym;
 | |
| 
 | |
|     /* Init the per sample phase shift complex numbers */
 | |
|     for( m=0; m<M; m++){
 | |
|         dosc_f[m] = comp_exp_j(2*M_PI*((float)(f1_tx+(fs_tx*m))/(float)(Fs)));
 | |
|     }
 | |
| 
 | |
|     bit_i = 0;
 | |
|     for( i=0; i<fsk->Nsym; i++){
 | |
|         sym = 0;
 | |
|         /* Pack the symbol number from the bit stream */
 | |
|         for( m=M; m>>=1; ){
 | |
|             uint8_t bit = tx_bits[bit_i];
 | |
|             bit = (bit==1)?1:0;
 | |
|             sym = (sym<<1)|bit;
 | |
|             bit_i++;
 | |
|         }
 | |
|         /* Look up symbol phase shift */
 | |
|         dph = dosc_f[sym];
 | |
|         /* Spin the oscillator for a symbol period */
 | |
|         for(j=0; j<Ts; j++){
 | |
|             tx_phase_c = cmult(tx_phase_c,dph);
 | |
|             fsk_out[i*Ts+j] = 2*tx_phase_c.real;
 | |
|         }
 | |
| 
 | |
|     }
 | |
| 
 | |
|     /* Normalize TX phase to prevent drift */
 | |
|     tx_phase_c = comp_normalize(tx_phase_c);
 | |
| 
 | |
|     /* save TX phase */
 | |
|     fsk->tx_phase_c = tx_phase_c;
 | |
| 
 | |
|     delete[] dosc_f;
 | |
| }
 | |
| 
 | |
| void fsk_mod_c(struct FSK *fsk,COMP fsk_out[],uint8_t tx_bits[]){
 | |
|     COMP tx_phase_c = fsk->tx_phase_c; /* Current complex TX phase */
 | |
|     int f1_tx = fsk->f1_tx;         /* '0' frequency */
 | |
|     int fs_tx = fsk->fs_tx;         /* space between frequencies */
 | |
|     int Ts = fsk->Ts;               /* samples-per-symbol */
 | |
|     int Fs = fsk->Fs;               /* sample freq */
 | |
|     int M = fsk->mode;
 | |
|     COMP *dosc_f = new COMP[M];     /* phase shift per sample */
 | |
|     COMP dph;                       /* phase shift of current bit */
 | |
|     int i, j, bit_i, sym;
 | |
|     int m;
 | |
| 
 | |
|     /* Init the per sample phase shift complex numbers */
 | |
|     for( m=0; m<M; m++){
 | |
|         dosc_f[m] = comp_exp_j(2*M_PI*((float)(f1_tx+(fs_tx*m))/(float)(Fs)));
 | |
|     }
 | |
| 
 | |
|     bit_i = 0;
 | |
|     for( i=0; i<fsk->Nsym; i++){
 | |
|         sym = 0;
 | |
|         /* Pack the symbol number from the bit stream */
 | |
|         for( m=M; m>>=1; ){
 | |
|             uint8_t bit = tx_bits[bit_i];
 | |
|             bit = (bit==1)?1:0;
 | |
|             sym = (sym<<1)|bit;
 | |
|             bit_i++;
 | |
|         }
 | |
|         /* Look up symbol phase shift */
 | |
|         dph = dosc_f[sym];
 | |
|         /* Spin the oscillator for a symbol period */
 | |
|         for(j=0; j<Ts; j++){
 | |
|             tx_phase_c = cmult(tx_phase_c,dph);
 | |
|             fsk_out[i*Ts+j] = fcmult(2,tx_phase_c);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /* Normalize TX phase to prevent drift */
 | |
|     tx_phase_c = comp_normalize(tx_phase_c);
 | |
| 
 | |
|     /* save TX phase */
 | |
|     fsk->tx_phase_c = tx_phase_c;
 | |
| 
 | |
|     delete[] dosc_f;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* Modulator that assume an external VCO.  The output is a voltage
 | |
|    that changes for each symbol */
 | |
| 
 | |
| void fsk_mod_ext_vco(struct FSK *fsk, float vco_out[], uint8_t tx_bits[]) {
 | |
|     int f1_tx = fsk->f1_tx;         /* '0' frequency */
 | |
|     int fs_tx = fsk->fs_tx;         /* space between frequencies */
 | |
|     int Ts = fsk->Ts;               /* samples-per-symbol */
 | |
|     int M = fsk->mode;
 | |
|     int i, j, m, sym, bit_i;
 | |
| 
 | |
|     bit_i = 0;
 | |
|     for(i=0; i<fsk->Nsym; i++) {
 | |
|         /* generate the symbol number from the bit stream,
 | |
|            e.g. 0,1 for 2FSK, 0,1,2,3 for 4FSK */
 | |
| 
 | |
|         sym = 0;
 | |
| 
 | |
|         /* unpack the symbol number from the bit stream */
 | |
| 
 | |
|         for( m=M; m>>=1; ){
 | |
|             uint8_t bit = tx_bits[bit_i];
 | |
|             bit = (bit==1)?1:0;
 | |
|             sym = (sym<<1)|bit;
 | |
|             bit_i++;
 | |
|         }
 | |
| 
 | |
|         /*
 | |
|            Map 'sym' to VCO frequency
 | |
|            Note: drive is inverted, a higher tone drives VCO voltage lower
 | |
|          */
 | |
| 
 | |
|         //fprintf(stderr, "i: %d sym: %d freq: %f\n", i, sym, f1_tx + fs_tx*(float)sym);
 | |
|         for(j=0; j<Ts; j++) {
 | |
|             vco_out[i*Ts+j] = f1_tx + fs_tx*(float)sym;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| void fsk_stats_normalise_eye(struct FSK *fsk, int normalise_enable) {
 | |
|     fsk->normalise_eye = normalise_enable;
 | |
| }
 | |
| 
 | |
| } // freeDV
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 |