osmocom-analog/src/eurosignal/dsp.c

322 lines
9.2 KiB
C

/* Eurosignal signal processing
*
* (C) 2019 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/>.
*/
#define CHAN euro->sender.kanal
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include "../libsample/sample.h"
#include "../libdebug/debug.h"
#include "eurosignal.h"
#include "dsp.h"
#define PI 3.1415927
/* signaling */
#define MAX_DEVIATION 5000.0 /* FIXME */
#define MAX_MODULATION 2000.0
#define TONE_DEVIATION 5000.0 /* FIXME */
#define TONE_INDEX 0.92
#define MAX_DISPLAY 1.4 /* something above tone level */
#define DIGIT_DURATION 0.1 /* duration of digit */
#define PAUSE_DURATION 0.22 /* duration of pause */
#define FREQUENCY_MIN 313.3
#define FREQUENCY_MAX 1153.1
#define FREQUENCY_TOL 15.0 /* tolerance of frequency */
#define DIGIT_DETECT 200 /* time for a tone to sustain (in samples) */
#define TIMEOUT_DETECT 4000 /* time for timeout detection (in samples) */
static struct dsp_digits {
char digit;
char *name;
double frequency;
double phaseshift65536;
} dsp_digits[] = {
{ 'I', "Idle", 1153.1, 0 },
{ 'R', "Repeat", 1062.9, 0 },
{ '0', "Digit 0", 979.8, 0 },
{ '1', "Digit 1", 903.1, 0 },
{ '2', "Digit 2", 832.5, 0 },
{ '3', "Digit 3", 767.4, 0 },
{ '4', "Digit 4", 707.4, 0 },
{ '5', "Digit 5", 652.0, 0 },
{ '6', "Digit 6", 601.0, 0 },
{ '7', "Digit 7", 554.0, 0 },
{ '8', "Digit 8", 510.7, 0 },
{ '9', "Digit 9", 470.8, 0 },
{ 'A', "Spare 1", 433.9, 0 },
{ 'B', "Spare 2", 400.0, 0 },
{ 'C', "Spare 3", 368.7, 0 },
{ 'D', "Spare 4", 339.9, 0 },
{ 'E', "Spare 5", 313.3, 0 },
{ '\0', NULL, 0.0, 0 },
};
static const char *digit_to_name(char digit)
{
int i;
for (i = 0; dsp_digits[i].digit; i++) {
if (dsp_digits[i].digit == digit)
return dsp_digits[i].name;
}
return "<none>";
}
static double digit_to_phaseshift65536(char digit)
{
int i;
for (i = 0; dsp_digits[i].digit; i++) {
if (dsp_digits[i].digit == digit)
return dsp_digits[i].phaseshift65536;
}
return 0.0;
}
static double dsp_tone[65536];
/* global init for FSK */
void dsp_init(int samplerate)
{
int i;
PDEBUG(DDSP, DEBUG_DEBUG, "Generating phase shiftings for tones.\n");
for (i = 0; dsp_digits[i].digit; i++)
dsp_digits[i].phaseshift65536 = 65536.0 / ((double)samplerate / dsp_digits[i].frequency);
PDEBUG(DDSP, DEBUG_DEBUG, "Generating sine table for tones.\n");
for (i = 0; i < 65536; i++)
dsp_tone[i] = sin((double)i / 65536.0 * 2.0 * PI);
}
/* Init transceiver instance. */
int dsp_init_sender(euro_t *euro, int samplerate, int fm)
{
int rc = 0;
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Init DSP for 'Sender'.\n");
/* set modulation parameters */
if (fm)
sender_set_fm(&euro->sender, MAX_DEVIATION, MAX_MODULATION, TONE_DEVIATION, MAX_DISPLAY);
else
sender_set_am(&euro->sender, MAX_MODULATION, 1.0, MAX_DISPLAY, TONE_INDEX);
euro->sample_duration = 1.0 / (double)samplerate;
/* initial phase shift */
euro->tx_phaseshift65536 = digit_to_phaseshift65536('I');
/* init demodulator */
rc = fm_demod_init(&euro->rx_demod, 8000, (FREQUENCY_MIN + FREQUENCY_MAX) / 2.0, FREQUENCY_MAX - FREQUENCY_MIN);
if (rc)
goto error;
/* use fourth order (2 iter) filter, since it is as fast as second order (1 iter) filter */
iir_lowpass_init(&euro->rx_lp, 25.0, 8000, 2);
euro->dmp_tone_level = display_measurements_add(&euro->sender.dispmeas, "Tone Level", "%.1f %%", DISPLAY_MEAS_LAST, DISPLAY_MEAS_LEFT, 0.0, 150.0, 100.0);
//euro->dmp_tone_quality = display_measurements_add(&euro->sender.dispmeas, "Tone Quality", "%.1f %%", DISPLAY_MEAS_LAST, DISPLAY_MEAS_LEFT, 0.0, 100.0, 100.0);
return 0;
error:
dsp_cleanup_sender(euro);
return -rc;
}
/* Cleanup transceiver instance. */
void dsp_cleanup_sender(euro_t *euro)
{
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Cleanup DSP for 'Sender'.\n");
/* cleanup demodulator */
fm_demod_exit(&euro->rx_demod);
}
//#define DEBUG
static void tone_decode(euro_t *euro, sample_t *samples, int length)
{
sample_t frequency[length], f;
sample_t I[length], Q[length];
int i, d;
char digit;
/* tone demodulation */
fm_demodulate_real(&euro->rx_demod, frequency, length, samples, I, Q);
/* reduce bandwidth of tone detector */
iir_process(&euro->rx_lp, frequency, length);
/* detect tone */
for (i = 0; i < length; i++) {
/* get frequency */
f = frequency[i] + (FREQUENCY_MIN + FREQUENCY_MAX) / 2.0;
#ifdef DEBUG
if (i == 0) printf("%s %.5f ", debug_amplitude(frequency[i] / (FREQUENCY_MAX - FREQUENCY_MIN) * 2.0), f);
#endif
for (d = 0; dsp_digits[d].digit; d++) {
if (f >= dsp_digits[d].frequency - FREQUENCY_TOL && f <= dsp_digits[d].frequency + FREQUENCY_TOL)
break;
}
#ifdef DEBUG
if (i == 0) printf("%c\n", dsp_digits[d].digit);
#endif
/* change detection and collect digits */
digit = dsp_digits[d].digit;
if (digit != euro->rx_digit_last) {
euro->rx_digit_last = digit;
euro->rx_digit_count = 0;
}
euro->rx_digit_count++;
switch (digit) {
case 'I':
/* pause tone */
if (euro->rx_digit_count == DIGIT_DETECT) {
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Detected Idle tone, starting.\n");
euro->rx_digit_receiving = 1;
euro->rx_digit_index = 0;
euro->rx_timeout_count = 0;
}
break;
case '\0':
/* we are not yet receiving digits */
if (!euro->rx_digit_receiving)
break;
if (euro->rx_digit_count == DIGIT_DETECT) {
/* out of range tone */
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Detected tone out of range, aborting.\n");
euro->rx_digit_receiving = 0;
}
break;
default:
/* we are not yet receiving digits */
if (!euro->rx_digit_receiving)
break;
/* got digit */
if (euro->rx_digit_count == DIGIT_DETECT) {
double level;
level = sqrt(I[i] * I[i] + Q[i] * Q[i]) * 2;
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Detected digit '%s' (level = %.0f%%)\n", digit_to_name(digit), level * 100.0);
display_measurements_update(euro->dmp_tone_level, level * 100.0, 0.0);
euro->rx_digits[euro->rx_digit_index] = digit;
euro->rx_digit_index++;
euro->rx_timeout_count = 0;
if (euro->rx_digit_index == 6 || euro->rx_digits[0] == 'R') {
euro->rx_digits[euro->rx_digit_index] = '\0';
euro_receive_id(euro, euro->rx_digits);
euro->rx_digit_receiving = 0;
}
break;
}
}
/* abort if tone sustains too long or next tone will not become steady */
if (euro->rx_digit_receiving && euro->rx_digit_index) {
euro->rx_timeout_count++;
if (euro->rx_timeout_count == TIMEOUT_DETECT) {
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Timeout receiving, aborting.\n");
euro->rx_digit_receiving = 0;
}
}
euro->rx_digit_last = digit;
}
}
/* Process received audio stream from radio unit. */
void sender_receive(sender_t *sender, sample_t *samples, int length, double __attribute__((unused)) rf_level_db)
{
euro_t *euro = (euro_t *) sender;
if (euro->rx) {
sample_t down[length];
int count;
/* downsample and decode */
memcpy(down, samples, sizeof(down)); // copy, so audio will not be corrupted at loopback
count = samplerate_downsample(&euro->sender.srstate, down, length);
tone_decode(euro, down, count);
}
}
/* Generate tone of paging digits. */
static void tone_send(euro_t *euro, sample_t *samples, int length)
{
int i;
for (i = 0; i < length; i++) {
if (!euro->tx_digits[0]) {
if (euro->tx_time >= PAUSE_DURATION) {
euro->tx_time -= PAUSE_DURATION;
euro_get_id(euro, euro->tx_digits);
euro->tx_digit_index = 0;
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Sending digit '%s'\n", digit_to_name(euro->tx_digits[0]));
euro->tx_phaseshift65536 = digit_to_phaseshift65536(euro->tx_digits[0]);
}
} else {
if (euro->tx_time >= DIGIT_DURATION) {
euro->tx_time -= DIGIT_DURATION;
if (++euro->tx_digit_index == 6) {
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Sending Idle tone'\n");
euro->tx_digits[0] = '\0';
euro->tx_phaseshift65536 = digit_to_phaseshift65536('I');
} else {
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Sending digit '%s'\n", digit_to_name(euro->tx_digits[euro->tx_digit_index]));
euro->tx_phaseshift65536 = digit_to_phaseshift65536(euro->tx_digits[euro->tx_digit_index]);
}
}
}
*samples++ = dsp_tone[(uint16_t)euro->tx_phase];
euro->tx_phase += euro->tx_phaseshift65536;
if (euro->tx_phase >= 65536.0)
euro->tx_phase -= 65536.0;
euro->tx_time += euro->sample_duration;
}
}
/* Provide stream of audio toward radio unit */
void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int length)
{
euro_t *euro = (euro_t *) sender;
if (euro->tx) {
memset(power, 1, length);
tone_send(euro, samples, length);
} else {
memset(power, 0, length);
memset(samples, 0, sizeof(*samples) * length);
}
}