/* FM modulation processing
 *
 * (C) 2017 by Andreas Eversberg <jolly@eversberg.eu>
 * All Rights Reserved
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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 General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include "../libsample/sample.h"
#include "fm.h"

static int has_init = 0;
static int fast_math = 0;
static float *sin_tab = NULL, *cos_tab = NULL;

/* global init */
int fm_init(int _fast_math)
{
	fast_math = _fast_math;

	if (fast_math) {
		int i;

		sin_tab = calloc(65536+16384, sizeof(*sin_tab));
		if (!sin_tab) {
			fprintf(stderr, "No mem!\n");
			return -ENOMEM;
		}
		cos_tab = sin_tab + 16384;

		/* generate sine and cosine */
		for (i = 0; i < 65536+16384; i++)
			sin_tab[i] = sin(2.0 * M_PI * (double)i / 65536.0);
	}

	has_init = 1;

	return 0;
}

/* global exit */
void fm_exit(void)
{
	if (sin_tab) {
		free(sin_tab);
		sin_tab = cos_tab = NULL;
	}

	has_init = 0;
}

/* init FM modulator */
int fm_mod_init(fm_mod_t *mod, double samplerate, double offset, double amplitude)
{
	int i;

	if (!has_init) {
		fprintf(stderr, "libfm was not initialized, plese fix!\n");
		abort();
	}

	memset(mod, 0, sizeof(*mod));
	mod->samplerate = samplerate;
	mod->offset = offset;
	mod->amplitude = amplitude;

	mod->ramp_length = samplerate * 0.001;
	mod->ramp_tab = calloc(mod->ramp_length, sizeof(*mod->ramp_tab));
	if (!mod->ramp_tab) {
		fprintf(stderr, "No mem!\n");
		return -ENOMEM;
	}
	mod->state = MOD_STATE_OFF;

	/* generate ramp up with ramp_length */
	for (i = 0; i < mod->ramp_length; i++)
		mod->ramp_tab[i] = 0.5 - cos(M_PI * i / mod->ramp_length) / 2.0;

	return 0;
}

void fm_mod_exit(fm_mod_t *mod)
{
	if (mod->ramp_tab) {
		free(mod->ramp_tab);
		mod->ramp_tab = NULL;
	}
}

/* do frequency modulation of samples and add them to existing baseband */
void fm_modulate_complex(fm_mod_t *mod, sample_t *frequency, uint8_t *power, int length, float *baseband)
{
	double dev, rate, phase, offset;
	int ramp, ramp_length;
	double *ramp_tab;
	double amplitude;

	rate = mod->samplerate;
	phase = mod->phase;
	offset = mod->offset;
	ramp = mod->ramp;
	ramp_length = mod->ramp_length;
	ramp_tab = mod->ramp_tab;
	amplitude = mod->amplitude;

again:
	switch (mod->state) {
	case MOD_STATE_ON:
		/* modulate */
		while (length) {
			/* is power is not set, ramp down */
			if (!(*power)) {
				mod->state = MOD_STATE_RAMP_DOWN;
				break;
			}
			/* deviation is defined by the frequency value and the offset */
			dev = offset + *frequency++;
			power++;
			length--;
			if (fast_math) {
				phase += 65536.0 * dev / rate;
				if (phase < 0.0)
					phase += 65536.0;
				else if (phase >= 65536.0)
					phase -= 65536.0;
				*baseband++ += cos_tab[(uint16_t)phase] * amplitude;
				*baseband++ += sin_tab[(uint16_t)phase] * amplitude;
			} else {
				phase += 2.0 * M_PI * dev / rate;
				if (phase < 0.0)
					phase += 2.0 * M_PI;
				else if (phase >= 2.0 * M_PI)
					phase -= 2.0 * M_PI;
				*baseband++ += cos(phase) * amplitude;
				*baseband++ += sin(phase) * amplitude;
			}
		}
		break;
	case MOD_STATE_RAMP_DOWN:
		while (length) {
			/* if power is set, ramp up */
			if (*power) {
				mod->state = MOD_STATE_RAMP_UP;
				break;
			}
			if (ramp == 0) {
				mod->state = MOD_STATE_OFF;
				break;
			}
			dev = offset + *frequency++;
			power++;
			length--;
			if (fast_math) {
				phase += 65536.0 * dev / rate;
				if (phase < 0.0)
					phase += 65536.0;
				else if (phase >= 65536.0)
					phase -= 65536.0;
				*baseband++ += cos_tab[(uint16_t)phase] * amplitude * ramp_tab[ramp];
				*baseband++ += sin_tab[(uint16_t)phase] * amplitude * ramp_tab[ramp];
			} else {
				phase += 2.0 * M_PI * dev / rate;
				if (phase < 0.0)
					phase += 2.0 * M_PI;
				else if (phase >= 2.0 * M_PI)
					phase -= 2.0 * M_PI;
				*baseband++ += cos(phase) * amplitude * ramp_tab[ramp];
				*baseband++ += sin(phase) * amplitude * ramp_tab[ramp];
			}
			ramp--;
		}
		break;
	case MOD_STATE_OFF:
		while (length) {
			/* if power is set, ramp up */
			if (*power) {
				mod->state = MOD_STATE_RAMP_UP;
				break;
			}
			/* deviation is defined by the frequency value and the offset */
			dev = offset + *frequency++;
			power++;
			length--;
			if (fast_math) {
				phase += 65536.0 * dev / rate;
				if (phase < 0.0)
					phase += 65536.0;
				else if (phase >= 65536.0)
					phase -= 65536.0;
				*baseband++ += cos_tab[(uint16_t)phase] * amplitude * ramp_tab[ramp];
				*baseband++ += sin_tab[(uint16_t)phase] * amplitude * ramp_tab[ramp];
			} else {
				phase += 2.0 * M_PI * dev / rate;
				if (phase < 0.0)
					phase += 2.0 * M_PI;
				else if (phase >= 2.0 * M_PI)
					phase -= 2.0 * M_PI;
				*baseband++ += cos(phase) * amplitude * ramp_tab[ramp];
				*baseband++ += sin(phase) * amplitude * ramp_tab[ramp];
			}
		}
		break;
	case MOD_STATE_RAMP_UP:
		while (length) {
			/* is power is not set, ramp down */
			if (!(*power)) {
				mod->state = MOD_STATE_RAMP_DOWN;
				break;
			}
			if (ramp == ramp_length - 1) {
				mod->state = MOD_STATE_ON;
				break;
			}
			/* deviation is defined by the frequency value and the offset */
			dev = offset + *frequency++;
			power++;
			length--;
			if (fast_math) {
				phase += 65536.0 * dev / rate;
				if (phase < 0.0)
					phase += 65536.0;
				else if (phase >= 65536.0)
					phase -= 65536.0;
				*baseband++ += cos_tab[(uint16_t)phase] * amplitude * ramp_tab[ramp];
				*baseband++ += sin_tab[(uint16_t)phase] * amplitude * ramp_tab[ramp];
			} else {
				phase += 2.0 * M_PI * dev / rate;
				if (phase < 0.0)
					phase += 2.0 * M_PI;
				else if (phase >= 2.0 * M_PI)
					phase -= 2.0 * M_PI;
				*baseband++ += cos(phase) * amplitude * ramp_tab[ramp];
				*baseband++ += sin(phase) * amplitude * ramp_tab[ramp];
			}
			ramp++;
		}
		break;
	}
	if (length)
		goto again;

	mod->phase = phase;
	mod->ramp = ramp;
}

/* init FM demodulator */
int fm_demod_init(fm_demod_t *demod, double samplerate, double offset, double bandwidth)
{
	if (!has_init) {
		fprintf(stderr, "libfm was not initialized, plese fix!\n");
		abort();
	}

	memset(demod, 0, sizeof(*demod));
	demod->samplerate = samplerate;

	if (fast_math)
		demod->rot = 65536.0 * -offset / samplerate;
	else
		demod->rot = 2 * M_PI * -offset / samplerate;

	/* use fourth order (2 iter) filter, since it is as fast as second order (1 iter) filter */
	iir_lowpass_init(&demod->lp[0], bandwidth / 2.0, samplerate, 2);
	iir_lowpass_init(&demod->lp[1], bandwidth / 2.0, samplerate, 2);

	return 0;
}

void fm_demod_exit(fm_demod_t __attribute__ ((unused)) *demod)
{
}

static inline float fast_tan(float z)
{
	const float n1 = 0.97239411f;
	const float n2 = -0.19194795f;
	return (n1 + n2 * z * z) * z;
}

static inline float fast_atan2(float y, float x)
{
	if (x != 0.0) {
		if (fabsf(x) > fabsf(y)) {
			const float z = y / x;
			if (x > 0.0) /* atan2(y,x) = atan(y/x) if x > 0 */
				return fast_tan(z);
			else if (y >= 0.0) /* atan2(y,x) = atan(y/x) + PI if x < 0, y >= 0 */
				return fast_tan(z) + M_PI;
			else /* atan2(y,x) = atan(y/x) - PI if x < 0, y < 0 */
				return fast_tan(z) - M_PI;
		} else { /* Use property atan(y/x) = PI/2 - atan(x/y) if |y/x| > 1 */
			const float z = x / y;
			if (y > 0.0) /* atan2(y,x) = PI/2 - atan(x/y) if |y/x| > 1, y > 0 */
				return -fast_tan(z) + M_PI_2;
			else /* atan2(y,x) = -PI/2 - atan(x/y) if |y/x| > 1, y < 0 */
				return -fast_tan(z) - M_PI_2;
		}
	} else {
		if (y > 0.0) /* x = 0, y > 0 */
			return M_PI_2;
		else if (y < 0.0) /* x = 0, y < 0 */
			return -M_PI_2;
	}
	return 0.0; /* x,y = 0. return 0, because NaN would harm further processing  */
}

/* do frequency demodulation of baseband and write them to samples */
void fm_demodulate_complex(fm_demod_t *demod, sample_t *frequency, int length, float *baseband, sample_t *I, sample_t *Q)
{
	double phase, rot, last_phase, dev, rate;
	double _sin, _cos;
	sample_t i, q;
	int s, ss;

	rate = demod->samplerate;
	phase = demod->phase;
	rot = demod->rot;
	for (s = 0, ss = 0; s < length; s++) {
		phase += rot;
		i = baseband[ss++];
		q = baseband[ss++];
		if (fast_math) {
			if (phase < 0.0)
				phase += 65536.0;
			else if (phase >= 65536.0)
				phase -= 65536.0;
			_sin = sin_tab[(uint16_t)phase];
			_cos = cos_tab[(uint16_t)phase];
		} else {
			if (phase < 0.0)
				phase += 2.0 * M_PI;
			else if (phase >= 2.0 * M_PI)
				phase -= 2.0 * M_PI;
			_sin = sin(phase);
			_cos = cos(phase);
		}
		I[s] = i * _cos - q * _sin;
		Q[s] = i * _sin + q * _cos;
	}
	demod->phase = phase;
	iir_process(&demod->lp[0], I, length);
	iir_process(&demod->lp[1], Q, length);
	last_phase = demod->last_phase;
	for (s = 0; s < length; s++) {
		if (fast_math)
			phase = fast_atan2(Q[s], I[s]);
		else
			phase = atan2(Q[s], I[s]);
		dev = (phase - last_phase) / 2 / M_PI;
		last_phase = phase;
		if (dev < -0.49)
			dev += 1.0;
		else if (dev > 0.49)
			dev -= 1.0;
		dev *= rate;
		frequency[s] = dev;
	}
	demod->last_phase = last_phase;
}

void fm_demodulate_real(fm_demod_t *demod, sample_t *frequency, int length, sample_t *baseband, sample_t *I, sample_t *Q)
{
	double phase, rot, last_phase, dev, rate;
	double _sin, _cos;
	sample_t i;
	int s, ss;

	rate = demod->samplerate;
	phase = demod->phase;
	rot = demod->rot;
	for (s = 0, ss = 0; s < length; s++) {
		phase += rot;
		i = baseband[ss++];
		if (fast_math) {
			if (phase < 0.0)
				phase += 65536.0;
			else if (phase >= 65536.0)
				phase -= 65536.0;
			_sin = sin_tab[(uint16_t)phase];
			_cos = cos_tab[(uint16_t)phase];
		} else {
			if (phase < 0.0)
				phase += 2.0 * M_PI;
			else if (phase >= 2.0 * M_PI)
				phase -= 2.0 * M_PI;
			_sin = sin(phase);
			_cos = cos(phase);
		}
		I[s] = i * _cos;
		Q[s] = i * _sin;
	}
	demod->phase = phase;
	iir_process(&demod->lp[0], I, length);
	iir_process(&demod->lp[1], Q, length);
	last_phase = demod->last_phase;
	for (s = 0; s < length; s++) {
		if (fast_math)
			phase = fast_atan2(Q[s], I[s]);
		else
			phase = atan2(Q[s], I[s]);
		dev = (phase - last_phase) / 2 / M_PI;
		last_phase = phase;
		if (dev < -0.49)
			dev += 1.0;
		else if (dev > 0.49)
			dev -= 1.0;
		dev *= rate;
		frequency[s] = dev;
	}
	demod->last_phase = last_phase;
}

