/* Eurosignal signal processing * * (C) 2019 by Andreas Eversberg * 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 . */ #define CHAN euro->sender.kanal #include #include #include #include #include #include #include #include #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 ""; } 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); } }