1 //
2 // This file is part of Dire Wolf, an amateur radio packet TNC.
3 //
4 // Copyright (C) 2013, 2014, 2015, 2016, 2019 John Langner, WB2OSZ
5 //
6 // This program is free software: you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation, either version 2 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program. If not, see <http://www.gnu.org/licenses/>.
18 //
19
20
21 /*------------------------------------------------------------------
22 *
23 * Name: multi_modem.c
24 *
25 * Purpose: Use multiple modems in parallel to increase chances
26 * of decoding less than ideal signals.
27 *
28 * Description: The initial motivation was for HF SSB where mistuning
29 * causes a shift in the audio frequencies. Here, we can
30 * have multiple modems tuned to staggered pairs of tones
31 * in hopes that one will be close enough.
32 *
33 * The overall structure opens the door to other approaches
34 * as well. For VHF FM, the tones should always have the
35 * right frequencies but we might want to tinker with other
36 * modem parameters instead of using a single compromise.
37 *
38 * Originally: The the interface application is in 3 places:
39 *
40 * (a) Main program (direwolf.c or atest.c) calls
41 * demod_init to set up modem properties and
42 * hdlc_rec_init for the HDLC decoders.
43 *
44 * (b) demod_process_sample is called for each audio sample
45 * from the input audio stream.
46 *
47 * (c) When a valid AX.25 frame is found, process_rec_frame,
48 * provided by the application, in direwolf.c or atest.c,
49 * is called. Normally this comes from hdlc_rec.c but
50 * there are a couple other special cases to consider.
51 * It can be called from hdlc_rec2.c if it took a long
52 * time to "fix" corrupted bits. aprs_tt.c constructs
53 * a fake packet when a touch tone message is received.
54 *
55 * New in version 0.9:
56 *
57 * Put an extra layer in between which potentially uses
58 * multiple modems & HDLC decoders per channel. The tricky
59 * part is picking the best one when there is more than one
60 * success and discarding the rest.
61 *
62 * New in version 1.1:
63 *
64 * Several enhancements provided by Fabrice FAURE:
65 *
66 * Additional types of attempts to fix a bad CRC.
67 * Optimized code to reduce execution time.
68 * Improved detection of duplicate packets from
69 * different fixup attempts.
70 * Set limit on number of packets in fix up later queue.
71 *
72 * New in version 1.6:
73 *
74 * FX.25. Previously a delay of a couple bits (or more accurately
75 * symbols) was fine because the decoders took about the same amount of time.
76 * Now, we can have an additional delay of up to 64 check bytes and
77 * some filler in the data portion. We can't simply wait that long.
78 * With normal AX.25 a couple frames can come and go during that time.
79 * We want to delay the duplicate removal while FX.25 block reception
80 * is going on.
81 *
82 *------------------------------------------------------------------*/
83
84 //#define DEBUG 1
85
86 #define DIGIPEATER_C
87
88 #include "direwolf.h"
89
90 #include <stdlib.h>
91 #include <string.h>
92 #include <assert.h>
93 #include <stdio.h>
94 #include <unistd.h>
95
96 #include "ax25_pad.h"
97 #include "textcolor.h"
98 #include "multi_modem.h"
99 #include "demod.h"
100 #include "hdlc_rec.h"
101 #include "hdlc_rec2.h"
102 #include "dlq.h"
103 #include "fx25.h"
104 #include "version.h"
105 #include "ais.h"
106
107
108
109 // Properties of the radio channels.
110
111 static struct audio_s *save_audio_config_p;
112
113
114 // Candidates for further processing.
115
116 static struct {
117 packet_t packet_p;
118 alevel_t alevel;
119 int is_fx25; // 1 for FX.25, 0 for regular AX.25.
120 retry_t retries; // For the old "fix bits" strategy, this is the
121 // number of bits that were modified to get a good CRC.
122 // It would be 0 to something around 4.
123 // For FX.25, it is the number of corrected.
124 // This could be from 0 thru 32.
125 int age;
126 unsigned int crc;
127 int score;
128 } candidate[MAX_CHANS][MAX_SUBCHANS][MAX_SLICERS];
129
130
131
132 //#define PROCESS_AFTER_BITS 2 // version 1.4. Was a little short for skew of PSK with different modem types, optional pre-filter
133
134 #define PROCESS_AFTER_BITS 3
135
136
137 static int process_age[MAX_CHANS];
138
139 static void pick_best_candidate (int chan);
140
141
142
143 /*------------------------------------------------------------------------------
144 *
145 * Name: multi_modem_init
146 *
147 * Purpose: Called at application start up to initialize appropriate
148 * modems and HDLC decoders.
149 *
150 * Input: Modem properties structure as filled in from the configuration file.
151 *
152 * Outputs:
153 *
154 * Description: Called once at application startup time.
155 *
156 *------------------------------------------------------------------------------*/
157
multi_modem_init(struct audio_s * pa)158 void multi_modem_init (struct audio_s *pa)
159 {
160 int chan;
161
162
163 /*
164 * Save audio configuration for later use.
165 */
166
167 save_audio_config_p = pa;
168
169 memset (candidate, 0, sizeof(candidate));
170
171 demod_init (save_audio_config_p);
172 hdlc_rec_init (save_audio_config_p);
173
174 for (chan=0; chan<MAX_CHANS; chan++) {
175 if (save_audio_config_p->achan[chan].medium == MEDIUM_RADIO) {
176 if (save_audio_config_p->achan[chan].baud <= 0) {
177 text_color_set(DW_COLOR_ERROR);
178 dw_printf("Internal error, chan=%d, %s, %d\n", chan, __FILE__, __LINE__);
179 save_audio_config_p->achan[chan].baud = DEFAULT_BAUD;
180 }
181 int real_baud = save_audio_config_p->achan[chan].baud;
182 if (save_audio_config_p->achan[chan].modem_type == MODEM_QPSK) real_baud = save_audio_config_p->achan[chan].baud / 2;
183 if (save_audio_config_p->achan[chan].modem_type == MODEM_8PSK) real_baud = save_audio_config_p->achan[chan].baud / 3;
184
185 process_age[chan] = PROCESS_AFTER_BITS * save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / real_baud ;
186 //crc_queue_of_last_to_app[chan] = NULL;
187 }
188 }
189
190 }
191
192
193 /*------------------------------------------------------------------------------
194 *
195 * Name: multi_modem_process_sample
196 *
197 * Purpose: Feed the sample into the proper modem(s) for the channel.
198 *
199 * Inputs: chan - Radio channel number
200 *
201 * audio_sample
202 *
203 * Description: In earlier versions we always had a one-to-one mapping with
204 * demodulators and HDLC decoders.
205 * This was added so we could have multiple modems running in
206 * parallel with different mark/space tones to compensate for
207 * mistuning of HF SSB signals.
208 * It was also possible to run multiple filters, for the same
209 * tones, in parallel (e.g. ABC).
210 *
211 * Version 1.2: Let's try something new for an experiment.
212 * We will have a single mark/space demodulator but multiple
213 * slicers, using different levels, each with its own HDLC decoder.
214 * We now have a separate variable, num_demod, which could be 1
215 * while num_subchan is larger.
216 *
217 * Version 1.3: Go back to num_subchan with single meaning of number of demodulators.
218 * We now have separate independent variable, num_slicers, for the
219 * mark/space imbalance compensation.
220 * num_demod, while probably more descriptive, should not exist anymore.
221 *
222 *------------------------------------------------------------------------------*/
223
224 static float dc_average[MAX_CHANS];
225
multi_modem_get_dc_average(int chan)226 int multi_modem_get_dc_average (int chan)
227 {
228 // Scale to +- 200 so it will like the deviation measurement.
229
230 return ( (int) ((float)(dc_average[chan]) * (200.0f / 32767.0f) ) );
231 }
232
233 __attribute__((hot))
multi_modem_process_sample(int chan,int audio_sample)234 void multi_modem_process_sample (int chan, int audio_sample)
235 {
236 int d;
237 int subchan;
238
239 // Accumulate an average DC bias level.
240 // Shouldn't happen with a soundcard but could with mistuned SDR.
241
242 dc_average[chan] = dc_average[chan] * 0.999f + (float)audio_sample * 0.001f;
243
244
245 // Issue 128. Someone ran into this.
246
247 //assert (save_audio_config_p->achan[chan].num_subchan > 0 && save_audio_config_p->achan[chan].num_subchan <= MAX_SUBCHANS);
248 //assert (save_audio_config_p->achan[chan].num_slicers > 0 && save_audio_config_p->achan[chan].num_slicers <= MAX_SLICERS);
249
250 if (save_audio_config_p->achan[chan].num_subchan <= 0 || save_audio_config_p->achan[chan].num_subchan > MAX_SUBCHANS ||
251 save_audio_config_p->achan[chan].num_slicers <= 0 || save_audio_config_p->achan[chan].num_slicers > MAX_SLICERS) {
252
253 text_color_set(DW_COLOR_ERROR);
254 dw_printf ("ERROR! Something is seriously wrong in %s %s.\n", __FILE__, __func__);
255 dw_printf ("chan = %d, num_subchan = %d [max %d], num_slicers = %d [max %d]\n", chan,
256 save_audio_config_p->achan[chan].num_subchan, MAX_SUBCHANS,
257 save_audio_config_p->achan[chan].num_slicers, MAX_SLICERS);
258 dw_printf ("Please report this message and include a copy of your configuration file.\n");
259 exit (EXIT_FAILURE);
260 }
261
262 /* Formerly one loop. */
263 /* 1.2: We can feed one demodulator but end up with multiple outputs. */
264
265 /* Send same thing to all. */
266 for (d = 0; d < save_audio_config_p->achan[chan].num_subchan; d++) {
267 demod_process_sample(chan, d, audio_sample);
268 }
269
270 for (subchan = 0; subchan < save_audio_config_p->achan[chan].num_subchan; subchan++) {
271 int slice;
272
273 for (slice = 0; slice < save_audio_config_p->achan[chan].num_slicers; slice++) {
274
275 if (candidate[chan][subchan][slice].packet_p != NULL) {
276 candidate[chan][subchan][slice].age++;
277 if (candidate[chan][subchan][slice].age > process_age[chan]) {
278 if (fx25_rec_busy(chan)) {
279 candidate[chan][subchan][slice].age = 0;
280 }
281 else {
282 pick_best_candidate (chan);
283 }
284 }
285 }
286 }
287 }
288 }
289
290
291
292 /*-------------------------------------------------------------------
293 *
294 * Name: multi_modem_process_rec_frame
295 *
296 * Purpose: This is called when we receive a frame with a valid
297 * FCS and acceptable size.
298 *
299 * Inputs: chan - Audio channel number, 0 or 1.
300 * subchan - Which modem found it.
301 * slice - Which slice found it.
302 * fbuf - Pointer to first byte in HDLC frame.
303 * flen - Number of bytes excluding the FCS.
304 * alevel - Audio level, range of 0 - 100.
305 * (Special case, use negative to skip
306 * display of audio level line.
307 * Use -2 to indicate DTMF message.)
308 * retries - Level of correction used.
309 * is_fx25 - 1 for FX.25, 0 for normal AX.25.
310 *
311 * Description: Add to list of candidates. Best one will be picked later.
312 *
313 *--------------------------------------------------------------------*/
314
315
multi_modem_process_rec_frame(int chan,int subchan,int slice,unsigned char * fbuf,int flen,alevel_t alevel,retry_t retries,int is_fx25)316 void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned char *fbuf, int flen, alevel_t alevel, retry_t retries, int is_fx25)
317 {
318 packet_t pp;
319
320
321 assert (chan >= 0 && chan < MAX_CHANS);
322 assert (subchan >= 0 && subchan < MAX_SUBCHANS);
323 assert (slice >= 0 && slice < MAX_SUBCHANS);
324
325 // Special encapsulation for AIS & EAS so they can be treated normally pretty much everywhere else.
326
327 if (save_audio_config_p->achan[chan].modem_type == MODEM_AIS) {
328 char nmea[256];
329 ais_to_nmea (fbuf, flen, nmea, sizeof(nmea));
330
331 char monfmt[276];
332 snprintf (monfmt, sizeof(monfmt), "AIS>%s%1d%1d:{%c%c%s", APP_TOCALL, MAJOR_VERSION, MINOR_VERSION, USER_DEF_USER_ID, USER_DEF_TYPE_AIS, nmea);
333 pp = ax25_from_text (monfmt, 1);
334
335 // alevel gets in there somehow making me question why it is passed thru here.
336 }
337 else if (save_audio_config_p->achan[chan].modem_type == MODEM_EAS) {
338 char monfmt[300]; // EAS SAME message max length is 268
339
340 snprintf (monfmt, sizeof(monfmt), "EAS>%s%1d%1d:{%c%c%s", APP_TOCALL, MAJOR_VERSION, MINOR_VERSION, USER_DEF_USER_ID, USER_DEF_TYPE_EAS, fbuf);
341 pp = ax25_from_text (monfmt, 1);
342
343 // alevel gets in there somehow making me question why it is passed thru here.
344 }
345 else {
346 pp = ax25_from_frame (fbuf, flen, alevel);
347 }
348 if (pp == NULL) {
349 text_color_set(DW_COLOR_ERROR);
350 dw_printf ("Unexpected internal problem, %s %d\n", __FILE__, __LINE__);
351 return; /* oops! why would it fail? */
352 }
353
354
355 /*
356 * If only one demodulator/slicer, and no FX.25 in progress,
357 * push it thru and forget about all this foolishness.
358 */
359 if (save_audio_config_p->achan[chan].num_subchan == 1 &&
360 save_audio_config_p->achan[chan].num_slicers == 1 &&
361 ! fx25_rec_busy(chan)) {
362
363
364 int drop_it = 0;
365 if (save_audio_config_p->recv_error_rate != 0) {
366 float r = (float)(rand()) / (float)RAND_MAX; // Random, 0.0 to 1.0
367
368 //text_color_set(DW_COLOR_INFO);
369 //dw_printf ("TEMP DEBUG. recv error rate = %d\n", save_audio_config_p->recv_error_rate);
370
371 if (save_audio_config_p->recv_error_rate / 100.0 > r) {
372 drop_it = 1;
373 text_color_set(DW_COLOR_INFO);
374 dw_printf ("Intentionally dropping incoming frame. Recv Error rate = %d per cent.\n", save_audio_config_p->recv_error_rate);
375 }
376 }
377
378 if (drop_it ) {
379 ax25_delete (pp);
380 }
381 else {
382 dlq_rec_frame (chan, subchan, slice, pp, alevel, is_fx25, retries, "");
383 }
384 return;
385 }
386
387
388 /*
389 * Otherwise, save them up for a few bit times so we can pick the best.
390 */
391 if (candidate[chan][subchan][slice].packet_p != NULL) {
392 /* Plain old AX.25: Oops! Didn't expect it to be there. */
393 /* FX.25: Quietly replace anything already there. It will have priority. */
394 ax25_delete (candidate[chan][subchan][slice].packet_p);
395 candidate[chan][subchan][slice].packet_p = NULL;
396 }
397
398 assert (pp != NULL);
399
400 candidate[chan][subchan][slice].packet_p = pp;
401 candidate[chan][subchan][slice].alevel = alevel;
402 candidate[chan][subchan][slice].is_fx25 = is_fx25;
403 candidate[chan][subchan][slice].retries = retries;
404 candidate[chan][subchan][slice].age = 0;
405 candidate[chan][subchan][slice].crc = ax25_m_m_crc(pp);
406 }
407
408
409
410
411 /*-------------------------------------------------------------------
412 *
413 * Name: pick_best_candidate
414 *
415 * Purpose: This is called when we have one or more candidates
416 * available for a certain amount of time.
417 *
418 * Description: Pick the best one and send it up to the application.
419 * Discard the others.
420 *
421 * Rules: We prefer one received perfectly but will settle for
422 * one where some bits had to be flipped to get a good CRC.
423 *
424 *--------------------------------------------------------------------*/
425
426 /* This is a suitable order for interleaved "G" demodulators. */
427 /* Opposite order would be suitable for multi-frequency although */
428 /* multiple slicers are of questionable value for HF SSB. */
429
430 #define subchan_from_n(x) ((x) % save_audio_config_p->achan[chan].num_subchan)
431 #define slice_from_n(x) ((x) / save_audio_config_p->achan[chan].num_subchan)
432
433
pick_best_candidate(int chan)434 static void pick_best_candidate (int chan)
435 {
436 int best_n, best_score;
437 char spectrum[MAX_SUBCHANS*MAX_SLICERS+1];
438 int n, j, k;
439 int num_bars = save_audio_config_p->achan[chan].num_slicers * save_audio_config_p->achan[chan].num_subchan;
440
441 memset (spectrum, 0, sizeof(spectrum));
442
443 for (n = 0; n < num_bars; n++) {
444 j = subchan_from_n(n);
445 k = slice_from_n(n);
446
447 /* Build the spectrum display. */
448
449 if (candidate[chan][j][k].packet_p == NULL) {
450 spectrum[n] = '_';
451 }
452 else if (candidate[chan][j][k].is_fx25) {
453 // FIXME: using retries both as an enum and later int too.
454 if ((int)(candidate[chan][j][k].retries) <= 9) {
455 spectrum[n] = '0' + candidate[chan][j][k].retries;
456 }
457 else {
458 spectrum[n] = '+';
459 }
460 }
461 else if (candidate[chan][j][k].retries == RETRY_NONE) {
462 spectrum[n] = '|';
463 }
464 else if (candidate[chan][j][k].retries == RETRY_INVERT_SINGLE) {
465 spectrum[n] = ':';
466 }
467 else {
468 spectrum[n] = '.';
469 }
470
471 /* Begining score depends on effort to get a valid frame CRC. */
472
473 if (candidate[chan][j][k].packet_p == NULL) {
474 candidate[chan][j][k].score = 0;
475 }
476 else {
477 if (candidate[chan][j][k].is_fx25) {
478 candidate[chan][j][k].score = 9000 - 100 * candidate[chan][j][k].retries;
479 }
480 else {
481 /* Originally, this produced 0 for the PASSALL case. */
482 /* This didn't work so well when looking for the best score. */
483 /* Around 1.3 dev H, we add an extra 1 in here so the minimum */
484 /* score should now be 1 for anything received. */
485
486 candidate[chan][j][k].score = RETRY_MAX * 1000 - ((int)candidate[chan][j][k].retries * 1000) + 1;
487 }
488 }
489 }
490
491 /* Bump it up slightly if others nearby have the same CRC. */
492
493 for (n = 0; n < num_bars; n++) {
494 int m;
495
496 j = subchan_from_n(n);
497 k = slice_from_n(n);
498
499 if (candidate[chan][j][k].packet_p != NULL) {
500
501 for (m = 0; m < num_bars; m++) {
502
503 int mj = subchan_from_n(m);
504 int mk = slice_from_n(m);
505
506 if (m != n && candidate[chan][mj][mk].packet_p != NULL) {
507 if (candidate[chan][j][k].crc == candidate[chan][mj][mk].crc) {
508 candidate[chan][j][k].score += (num_bars+1) - abs(m-n);
509 }
510 }
511 }
512 }
513 }
514
515 best_n = 0;
516 best_score = 0;
517
518 for (n = 0; n < num_bars; n++) {
519 j = subchan_from_n(n);
520 k = slice_from_n(n);
521
522 if (candidate[chan][j][k].packet_p != NULL) {
523 if (candidate[chan][j][k].score > best_score) {
524 best_score = candidate[chan][j][k].score;
525 best_n = n;
526 }
527 }
528 }
529
530 #if DEBUG
531 text_color_set(DW_COLOR_DEBUG);
532 dw_printf ("\n%s\n", spectrum);
533
534 for (n = 0; n < num_bars; n++) {
535 j = subchan_from_n(n);
536 k = slice_from_n(n);
537
538 if (candidate[chan][j][k].packet_p == NULL) {
539 dw_printf ("%d.%d.%d: ptr=%p\n", chan, j, k,
540 candidate[chan][j][k].packet_p);
541 }
542 else {
543 dw_printf ("%d.%d.%d: ptr=%p, is_fx25=%d, retry=%d, age=%3d, crc=%04x, score=%d %s\n", chan, j, k,
544 candidate[chan][j][k].packet_p,
545 candidate[chan][j][k].is_fx25,
546 (int)(candidate[chan][j][k].retries),
547 candidate[chan][j][k].age,
548 candidate[chan][j][k].crc,
549 candidate[chan][j][k].score,
550 (n == best_n) ? "***" : "");
551 }
552 }
553 #endif
554
555 if (best_score == 0) {
556 text_color_set(DW_COLOR_ERROR);
557 dw_printf ("Unexpected internal problem, %s %d. How can best score be zero?\n", __FILE__, __LINE__);
558 }
559
560 /*
561 * send the best one along.
562 */
563
564 /* Delete those not chosen. */
565
566 for (n = 0; n < num_bars; n++) {
567 j = subchan_from_n(n);
568 k = slice_from_n(n);
569 if (n != best_n && candidate[chan][j][k].packet_p != NULL) {
570 ax25_delete (candidate[chan][j][k].packet_p);
571 candidate[chan][j][k].packet_p = NULL;
572 }
573 }
574
575 /* Pass along one. */
576
577
578 j = subchan_from_n(best_n);
579 k = slice_from_n(best_n);
580
581 int drop_it = 0;
582 if (save_audio_config_p->recv_error_rate != 0) {
583 float r = (float)(rand()) / (float)RAND_MAX; // Random, 0.0 to 1.0
584
585 //text_color_set(DW_COLOR_INFO);
586 //dw_printf ("TEMP DEBUG. recv error rate = %d\n", save_audio_config_p->recv_error_rate);
587
588 if (save_audio_config_p->recv_error_rate / 100.0 > r) {
589 drop_it = 1;
590 text_color_set(DW_COLOR_INFO);
591 dw_printf ("Intentionally dropping incoming frame. Recv Error rate = %d per cent.\n", save_audio_config_p->recv_error_rate);
592 }
593 }
594
595 if ( drop_it ) {
596 ax25_delete (candidate[chan][j][k].packet_p);
597 candidate[chan][j][k].packet_p = NULL;
598 }
599 else {
600 assert (candidate[chan][j][k].packet_p != NULL);
601 dlq_rec_frame (chan, j, k,
602 candidate[chan][j][k].packet_p,
603 candidate[chan][j][k].alevel,
604 candidate[chan][j][k].is_fx25,
605 (int)(candidate[chan][j][k].retries),
606 spectrum);
607
608 /* Someone else owns it now and will delete it later. */
609 candidate[chan][j][k].packet_p = NULL;
610 }
611
612 /* Clear in preparation for next time. */
613
614 memset (candidate[chan], 0, sizeof(candidate[chan]));
615
616 } /* end pick_best_candidate */
617
618
619 /* end multi_modem.c */
620