mirror of https://github.com/drowe67/codec2.git
283 lines
8.8 KiB
C
283 lines
8.8 KiB
C
/*---------------------------------------------------------------------------*\
|
|
|
|
FILE........: fm.c
|
|
AUTHOR......: David Rowe
|
|
DATE CREATED: February 2015
|
|
|
|
Functions that implement analog FM modulation and demodulation, see
|
|
also octave/fm.m.
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
/*
|
|
Copyright (C) 2015 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
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
#define FILT_MEM 200
|
|
|
|
/*---------------------------------------------------------------------------*\
|
|
|
|
INCLUDES
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "codec2_fm.h"
|
|
#include "comp_prim.h"
|
|
#include "fm_fir_coeff.h"
|
|
|
|
/*---------------------------------------------------------------------------*\
|
|
|
|
FUNCTIONS
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
/*---------------------------------------------------------------------------*\
|
|
|
|
FUNCTION....: fm_create
|
|
AUTHOR......: David Rowe
|
|
DATE CREATED: 24 Feb 2015
|
|
|
|
Create and initialise an instance of the "modem". Returns a pointer
|
|
to the modem states or NULL on failure. One set of states is
|
|
sufficient for a full duplex modem.
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
struct FM *fm_create(int nsam) {
|
|
struct FM *fm;
|
|
|
|
fm = (struct FM *)malloc(sizeof(struct FM));
|
|
if (fm == NULL) return NULL;
|
|
fm->rx_bb = (COMP *)malloc(sizeof(COMP) * (FILT_MEM + nsam));
|
|
assert(fm->rx_bb != NULL);
|
|
|
|
fm->rx_bb_filt_prev.real = 0.0;
|
|
fm->rx_bb_filt_prev.imag = 0.0;
|
|
fm->lo_phase.real = 1.0;
|
|
fm->lo_phase.imag = 0.0;
|
|
|
|
fm->tx_phase = 0;
|
|
|
|
fm->rx_dem_mem = (float *)malloc(sizeof(float) * (FILT_MEM + nsam));
|
|
assert(fm->rx_dem_mem != NULL);
|
|
|
|
fm->nsam = nsam;
|
|
|
|
return fm;
|
|
}
|
|
|
|
void fm_destroy(struct FM *fm_states) {
|
|
free(fm_states->rx_bb);
|
|
free(fm_states->rx_dem_mem);
|
|
free(fm_states);
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*\
|
|
|
|
FUNCTION....: fm_demod
|
|
AUTHOR......: David Rowe
|
|
DATE CREATED: 24 Feb 2015
|
|
|
|
Demodulate a FM signal to baseband audio.
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
void fm_demod(struct FM *fm_states, float rx_out[], float rx[]) {
|
|
float Fs = fm_states->Fs;
|
|
float fc = fm_states->fc;
|
|
float wc = 2 * M_PI * fc / Fs;
|
|
float fd = fm_states->fd;
|
|
float wd = 2 * M_PI * fd / Fs;
|
|
COMP *rx_bb = fm_states->rx_bb + FILT_MEM;
|
|
COMP wc_rect, rx_bb_filt, rx_bb_diff;
|
|
float rx_dem;
|
|
/*
|
|
float acc;
|
|
*/
|
|
float *rx_dem_mem = fm_states->rx_dem_mem + FILT_MEM;
|
|
int nsam = fm_states->nsam;
|
|
float mag;
|
|
int i, k;
|
|
|
|
wc_rect.real = cosf(wc);
|
|
wc_rect.imag = -sinf(wc);
|
|
|
|
for (i = 0; i < nsam; i++) {
|
|
/* down to complex baseband */
|
|
|
|
fm_states->lo_phase = cmult(fm_states->lo_phase, wc_rect);
|
|
rx_bb[i] = fcmult(rx[i], fm_states->lo_phase);
|
|
|
|
/* input FIR filter */
|
|
|
|
rx_bb_filt.real = 0.0;
|
|
rx_bb_filt.imag = 0.0;
|
|
|
|
for (k = 0; k < FILT_MEM / 2; k++) {
|
|
rx_bb_filt.real += rx_bb[i - k].real * bin[k + FILT_MEM / 4];
|
|
rx_bb_filt.imag += rx_bb[i - k].imag * bin[k + FILT_MEM / 4];
|
|
}
|
|
|
|
// rx_bb_filt = rx_bb[i];
|
|
// printf("%f %f %f\n", rx[i], wc_rect.real, wc_rect.imag);
|
|
// printf("%f %f %f\n", rx[i], fm_states->lo_phase.real,
|
|
// fm_states->lo_phase.imag); printf("%f %f %f\n", rx[i], rx_bb[i].real,
|
|
// rx_bb[i].imag); printf("%f %f\n", rx_bb_filt.real, rx_bb_filt.imag);
|
|
/*
|
|
Differentiate first, in rect domain, then find angle, this
|
|
puts signal on the positive side of the real axis and helps
|
|
atan2() behaive.
|
|
*/
|
|
|
|
rx_bb_diff = cmult(rx_bb_filt, cconj(fm_states->rx_bb_filt_prev));
|
|
fm_states->rx_bb_filt_prev = rx_bb_filt;
|
|
|
|
rx_dem = atan2f(rx_bb_diff.imag, rx_bb_diff.real);
|
|
|
|
/* limit maximum phase jumps, to remove static type noise at low SNRs */
|
|
|
|
if (rx_dem > wd) rx_dem = wd;
|
|
if (rx_dem < -wd) rx_dem = -wd;
|
|
|
|
rx_dem *= (1 / wd);
|
|
// printf("%f %f\n", rx_bb_diff.real, rx_bb_diff.imag);
|
|
rx_dem_mem[i] = rx_dem;
|
|
/*
|
|
acc = 0;
|
|
for(k=0; k<FILT_MEM; k++) {
|
|
acc += rx_dem_mem[i-k] * bout[k];
|
|
}
|
|
*/
|
|
rx_out[i] = rx_dem;
|
|
}
|
|
|
|
/* update filter memories */
|
|
|
|
rx_bb -= FILT_MEM;
|
|
rx_dem_mem -= FILT_MEM;
|
|
for (i = 0; i < FILT_MEM; i++) {
|
|
rx_bb[i] = rx_bb[i + nsam];
|
|
rx_dem_mem[i] = rx_dem_mem[i + nsam];
|
|
}
|
|
|
|
/* normalise digital oscillator as the magnitude can drift over time */
|
|
|
|
mag = cabsolute(fm_states->lo_phase);
|
|
fm_states->lo_phase.real /= mag;
|
|
fm_states->lo_phase.imag /= mag;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*\
|
|
|
|
FUNCTION....: fm_mod
|
|
AUTHOR......: Brady O'Brien
|
|
DATE CREATED: Sept. 10 2015
|
|
|
|
Modulate an FM signal from a baseband modulating signal
|
|
|
|
struct FM *fm - FM state structure. Can be reused from fm_demod.
|
|
float tx_in[] - nsam baseband samples to be modulated
|
|
float tx_out[] - nsam samples in which to place the modulated FM
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
void fm_mod(struct FM *fm_states, float tx_in[], float tx_out[]) {
|
|
float Fs = fm_states->Fs; // Sampling freq
|
|
float fc = fm_states->fc; // Center freq
|
|
float wc = 2 * M_PI * fc / Fs; // Center freq in rads/samp
|
|
float fd = fm_states->fd; // Max deviation in cycles/samp
|
|
float wd = 2 * M_PI * fd / Fs; // Max deviation in rads/samp
|
|
int nsam = fm_states->nsam; // Samples per batch of modulation
|
|
float tx_phase = fm_states->tx_phase; // Transmit phase in rads
|
|
float w; // Temp variable for phase of VFO during loop
|
|
int i;
|
|
|
|
// Go through the samples, spin the oscillator, and generate some FM
|
|
for (i = 0; i < nsam; i++) {
|
|
w = wc + wd * tx_in[i]; // Calculate phase of VFO
|
|
tx_phase += w; // Spin TX oscillator
|
|
|
|
// TODO: Add pre-emphasis and pre-emph AGC for voice
|
|
|
|
// Make sure tx_phase stays from 0 to 2PI.
|
|
// If tx_phase goes above 4PI, It's because fc+fd*tx_in[i] is way too large
|
|
// for the sample
|
|
// rate.
|
|
if (tx_phase > 2 * M_PI) tx_phase -= 2 * M_PI;
|
|
tx_out[i] = cosf(tx_phase);
|
|
}
|
|
// Save phase back into state struct
|
|
fm_states->tx_phase = tx_phase;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*\
|
|
|
|
FUNCTION....: fm_mod
|
|
AUTHOR......: Brady O'Brien
|
|
DATE CREATED: Sept. 10 2015
|
|
|
|
Modulate an FM signal from a baseband modulating signal. Output signal is
|
|
in complex domain
|
|
|
|
struct FM *fm - FM state structure. Can be reused from fm_demod.
|
|
float tx_in[] - nsam baseband samples to be modulated
|
|
COMP tx_out[] - nsam samples in which to place the modulated FM
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
void fm_mod_comp(struct FM *fm_states, float tx_in[], COMP tx_out[]) {
|
|
float Fs = fm_states->Fs; // Sampling freq
|
|
float fc = fm_states->fc; // Center freq
|
|
float wc = 2 * M_PI * fc / Fs; // Center freq in rads/samp
|
|
float fd = fm_states->fd; // Max deviation in cycles/samp
|
|
float wd = 2 * M_PI * fd / Fs; // Max deviation in rads/samp
|
|
int nsam = fm_states->nsam; // Samples per batch of modulation
|
|
float tx_phase = fm_states->tx_phase; // Transmit phase in rads
|
|
float w; // Temp variable for phase of VFO during loop
|
|
int i;
|
|
|
|
// Go through the samples, spin the oscillator, and generate some FM
|
|
for (i = 0; i < nsam; i++) {
|
|
w = wc + wd * tx_in[i]; // Calculate phase of VFO
|
|
tx_phase += w; // Spin TX oscillator
|
|
|
|
// TODO: Add pre-emphasis and pre-emph AGC for voice
|
|
|
|
// Make sure tx_phase stays from 0 to 2PI.
|
|
// If tx_phase goes above 4PI, It's because fc+fd*tx_in[i] is way too large
|
|
// for the sample
|
|
// rate.
|
|
if (tx_phase > 2 * M_PI) tx_phase -= 2 * M_PI;
|
|
|
|
tx_out[i].real = cosf(tx_phase);
|
|
tx_out[i].imag = sinf(tx_phase);
|
|
}
|
|
// Save phase back into state struct
|
|
fm_states->tx_phase = tx_phase;
|
|
}
|