1 // ---------------------------------------------------------------------
2 //
3 //  navtex.cxx
4 //
5 // Copyright (C) 2011-2016
6 //      Remi Chateauneu, F4ECW
7 //      Rik van Riel, AB1KW, <riel@surriel.com>
8 //
9 // This file is part of fldigi.  Adapted from code contained in JNX
10 // source code distribution.
11 //  JNX Copyright (C) Paul Lutus
12 // http://www.arachnoid.com/JNX/index.html
13 //
14 // fldigi is free software; you can redistribute it and/or modify
15 // it under the terms of the GNU General Public License as published by
16 // the Free Software Foundation; either version 3 of the License, or
17 // (at your option) any later version.
18 //
19 // fldigi is distributed in the hope that it will be useful,
20 // but WITHOUT ANY WARRANTY; without even the implied warranty of
21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 // GNU General Public License for more details.
23 //
24 // You should have received a copy of the GNU General Public License
25 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
26 // ---------------------------------------------------------------------
27 
28 // ---------------------------------------------------------------------
29 // Sync using multicorrelator, instead of null crossings
30 //      Rik van Riel, AB1KW, <riel@surriel.com>
31 //
32 // Null crossings are somewhat noisy, and the code to keep the navtex
33 // decoder in sync with the incoming signal using null crossings was
34 // rather fragile.
35 //
36 // Use a multicorrelator instead, which relies on the averaged magnitude
37 // of the signal accumulator to sync the decoder with the incoming signal.
38 //
39 // When debugging the code, the multicorrelator mostly corrects the
40 // modem forward in time, which can be explained by the fact that a
41 // bit takes 110.25 samples, while the code uses 110. When the NAVTEX
42 // transmitter is running at exactly 100 baud, one can expect to see
43 // the decoder get adjusted 25 times a second, to make up for the
44 // difference between 11000 and 11025.
45 //
46 // When multiple signals are on the air simultaneously, the null crossing
47 // code would often lose track of the signal. The multicorrelator seems
48 // to be more stable in this situation, though of course when both signals
49 // are close in strength things do not get decoded right.
50 //
51 // The signal sampling spread of 1/6 of the width of a bit was set through
52 // trial and error. A larger spread increases the signal difference between
53 // early, prompt, and late samples, but reduces the accumulator value seen
54 // by the demodulator. A smaller spread increases the accumulator value seen,
55 // but makes it harder to lock on in noisy conditions.
56 // ---------------------------------------------------------------------
57 
58 // ---------------------------------------------------------------------
59 // low pass mark & space individually
60 //      Rik van Riel, AB1KW, <riel@surriel.com>
61 //
62 // Putting individual low pass filters on mark and space seems to
63 // result in an improved ability to overcome pulse noise, and decode
64 // weaker navtex signals.
65 //
66 // I have not found any signal where the performance of the codec
67 // got worse with this change.
68 // ---------------------------------------------------------------------
69 
70 // ---------------------------------------------------------------------
71 // Correct display metric
72 //      Rik van Riel, AB1KW, <riel@surriel.com>
73 //
74 // The NAVTEX display_metric() function was buggy, in that decayavg
75 // returns the decayed value, but does not store it. It always put
76 // the current value in the metric, and kept avg_ratio at 0.0.
77 //
78 // This resulted in a somewhat chaotic, and not very useful metric
79 // display. Copy over the S/N calculation from the RTTY code, because
80 // that code seems to work well.
81 
82 // Also print the S/N in Status2, like the RTTY code and other modes
83 // do.
84 
85 // Copying over the RTTY S/N code wholesale might still not be
86 // enough, since the NAVTEX wave form appears to be somewhat
87 // different from RTTY.  However, at least we have something
88 // now, and the metric used for squelch seems to work again.
89 // ---------------------------------------------------------------------
90 
91 // ---------------------------------------------------------------------
92 // Correct display metric
93 //      Rik van Riel, AB1KW, <riel@surriel.com>
94 //
95 // Widen afc filter for 'jump 90 Hz' code
96 //
97 // When the NAVTEX code spots a power imbalance of more than a factor
98 // 5 between mark and space, it will shift the frequency by 90 Hz.
99 // This is reported to help with some signals.
100 //
101 // However, it breaks with some other signals, which have a different
102 // spectral distribution between mark and space, with a spectrum looking
103 // something like this:
104 //
105 //                                        *
106 //                                        *
107 //                                        *
108 //                                       **
109 //     ******                           ***
110 //    ********                         ******
111 //   **********                       ********
112 //  ********************************************
113 // **********************************************
114 //
115 // In this spectrum, mark & space have a similar amount of energy,
116 // but that is only apparent when the comparison between them is
117 // done on a wider sample than 10 Hz.
118 //
119 // Sampling 30 Hz instead seems to result in a more stable AFC.
120 // ---------------------------------------------------------------------
121 
122 // ---------------------------------------------------------------------
123 // use exact bit length
124 //      Rik van Riel, AB1KW, <riel@surriel.com>
125 //
126 // With a baud rate of 100 and a sample rate of 11025, the number
127 // of bits per sample is 110.25.  Approximating this with 110 bits
128 // per sample results in the decoder continuously chasing after the
129 // signal, and losing it more easily during transient noise or
130 // interference events.
131 //
132 // Simply changing the variable type from int to double makes life
133 // a little easier on the bit tracking code.
134 //
135 // The accumulator does not seem to care that it gets an extra sample
136 // every 4 bit periods.
137 // ---------------------------------------------------------------------
138 
139 // ---------------------------------------------------------------------
140 // improvements to the multi correlator
141 //      Rik van Riel, AB1KW, <riel@surriel.com>
142 //
143 // While the multi correlator for bit sync was a nice improvement over
144 // the null crossing tracking, it did lose sync too easily in the presence
145 // of transient noise or interference, and was full of magic adjustments.
146 //
147 // Replace the magic adjustments with a calculation, which makes the multi
148 // correlator able to ride out transient noise or interference, and then
149 // make a larger adjustment all at once (if needed).
150 // ---------------------------------------------------------------------
151 
152 // ---------------------------------------------------------------------
153 // use same mark/space detector as RTTY modem
154 //      Rik van Riel, AB1KW, <riel@surriel.com>
155 //
156 // Switch the NAVTEX modem over to the same mark/spac decoder, with W7AY's
157 // automatic threshold correction algorithm, like the RTTY modem uses.
158 //
159 // The noise subtraction is a little different than in the RTTY modem;
160 // the algorithm used in W7AY's code seems to work a little better with
161 // the noise present at 518 kHz, when compared to the algorithm used in
162 // the RTTY modem.
163 //
164 // I have compared this detector to a correlation detector; the latter
165 // appears to be a little more sensitive, which includes higher
166 // sensitivity to noise. With a 250 Hz filter on the radio, the
167 // correlation detector might be a little bit better, while with the
168 // filter on the radio opened up to 4kHz wide, this detector appears
169 // to be more robust.
170 //
171 // On signals with a large mark/space power imbalance, or where the power
172 // distribution in one of the two throws off the automatic frequency
173 // correction, this decoder is able to handle signals that neither of
174 // the alternatives tested does.
175 // ---------------------------------------------------------------------
176 
177 #include <math.h>
178 #include <stdlib.h>
179 #include <stdio.h>
180 #include <ctype.h>
181 #include <memory.h>
182 #include <assert.h>
183 
184 #include <list>
185 #include <vector>
186 #include <string>
187 #include <queue>
188 #include <deque>
189 #include <map>
190 #include <iostream>
191 #include <sstream>
192 #include <fstream>
193 #include <stdexcept>
194 
195 #include "config.h"
196 #include "configuration.h"
197 #include "fl_digi.h"
198 #include "debug.h"
199 #include "gettext.h"
200 #include "navtex.h"
201 #include "logbook.h"
202 #include "coordinate.h"
203 #include "misc.h"
204 #include "status.h"
205 #include "strutil.h"
206 #include "kmlserver.h"
207 #include "record_loader.h"
208 #include "fftfilt.h"
209 
210 #include "strutil.h"
211 
212 #include "FL/fl_ask.H"
213 
214 pthread_mutex_t  navtex_filter_mutex = PTHREAD_MUTEX_INITIALIZER;
215 
216 /// This models a line of the file defining Navtex stations.
217 class NavtexRecord
218 {
219 	std::string       m_country ;
220 	std::string       m_country_code ;
221 	double            m_frequency ;
222 	char              m_origin ;
223 	std::string       m_callsign ;
224 	std::string       m_name ;
225 	CoordinateT::Pair m_coordinates ;
226 
227 	std::string       m_locator ;
228 
229 	/// Reads a CSV file.
230 	static const char m_delim = ';';
231 public:
NavtexRecord()232 	NavtexRecord()
233 	: m_frequency(0.0)
234 	, m_origin('?')
235 	, m_name( _("Unknown station") ) {}
236 
origin(void) const237 	char origin(void) const { return m_origin; };
coordinates() const238 	const CoordinateT::Pair & coordinates() const { return m_coordinates; }
frequency(void) const239 	double frequency(void) const { return m_frequency; };
country() const240 	const std::string & country() const { return m_country; }
name() const241 	const std::string & name() const { return m_name; }
callsign() const242 	const std::string & callsign() const { return m_callsign; }
243 
244 	/// Example: Azores;AZR;490.0;J;CTH;Horta;38 32 N;28 38 W;II;PP
operator >>(std::istream & istrm,NavtexRecord & rec)245 	friend std::istream & operator>>( std::istream &  istrm, NavtexRecord & rec )
246 	{
247 		std::string input_str ;
248 		if( ! std::getline( istrm, input_str ) ) return istrm ;
249 		std::stringstream str_strm( input_str );
250 
251 		if( read_until_delim( m_delim, str_strm, rec.m_country                 )
252 		&&  read_until_delim( m_delim, str_strm  /* Country code */            )
253 		&&  read_until_delim( m_delim, str_strm, rec.m_frequency               )
254 		&&  read_until_delim( m_delim, str_strm, rec.m_origin                  )
255 		&&  read_until_delim( m_delim, str_strm, rec.m_callsign                )
256 		&&  read_until_delim( m_delim, str_strm, rec.m_name                    )
257 		&&  read_until_delim( m_delim, str_strm, rec.m_coordinates.latitude()  )
258 		&&  read_until_delim( m_delim, str_strm, rec.m_coordinates.longitude() )
259 		&&  read_until_delim( m_delim, str_strm  /* Zone */                    )
260 		&&  read_until_delim( m_delim, str_strm  /* Language */                )
261 
262 		&& ( rec.m_coordinates.latitude().is_lon() == false  )
263 		&& ( rec.m_coordinates.longitude().is_lon() == true  )
264 		)
265 		{
266 			return istrm ;
267 		}
268 
269 		istrm.setstate(std::ios::eofbit);
270 		return istrm ;
271 	}
272 };
273 
274 /// Navtex catalog of stations is used when logging to ADIF file: It gives the station name, callsign etc...
275 class NavtexCatalog : public RecordLoader< NavtexCatalog >
276 {
277 	// TODO: Consider a multimap<char, NavtexRecord>
278 	typedef std::deque< NavtexRecord > CatalogType ;
279 	CatalogType m_catalog ;
280 
281 	/// Frequency more or less 1 %: 485-494 kHz, 512-523 kHz etc...
freq_close(double freqA,double freqB)282 	static bool freq_close( double freqA, double freqB )
283 	{
284 		static const double freq_ratio = 1.01 ;
285 		return ( freqA < freqB * freq_ratio ) || ( freqA * freq_ratio > freqB );
286 	}
287 
288 	/// Tells if this is a reasonable Navtex frequency.
freq_acceptable(double freq)289 	static bool freq_acceptable( double freq )
290 	{
291 		return  freq_close( freq, 490.0 )
292 		||  freq_close( freq, 518.0 )
293 		||  freq_close( freq, 4209.5 );
294 	}
295 
Clear()296 	void Clear() {
297 		m_catalog.clear();
298 	}
299 
ReadRecord(std::istream & istrm)300 	bool ReadRecord( std::istream & istrm ) {
301 		NavtexRecord tmp ;
302 		istrm >> tmp ;
303 		if( istrm || istrm.eof() ) {
304 			m_catalog.push_back( tmp );
305 			return true ;
306 		}
307 		return false ;
308 	}
309 
310 	/// Minimal edit distance (Levenshtein) between the pattern and any token of the string.
DistToStationName(const std::string & msg,const std::string & pattern)311 	static double DistToStationName( const std::string & msg, const std::string & pattern ) {
312 		std::stringstream strm( msg );
313 		/// Any big number is OK, if bigger than any string length.
314 		double currDist = 1.7976931348623157e+308; // DBL_MAX ;
315 		typedef std::istream_iterator<std::string> StrmIterStr ;
316 		for( StrmIterStr itStrm( strm ); itStrm != StrmIterStr(); ++itStrm ) {
317 			const std::string tmp = *itStrm ;
318 			currDist = std::min( currDist, (double)levenshtein( tmp, pattern ) );
319 		}
320 		return currDist ;
321 	}
322 
323 public:
base_filename() const324 	std::string base_filename() const
325 	{
326 		return "NAVTEX_Stations.csv";
327 	}
328 
Description() const329 	const char * Description() const
330 	{
331 		return _("Navtex stations");
332 	}
333 
334 	/// Usual frequencies are 490, 518 or 4209 kiloHertz.
FindStation(long long freq_ll,char origin,const std::string & maidenhead,const std::string & msg)335 	const NavtexRecord * FindStation(
336 		long long freq_ll,
337 		char origin,
338 		const std::string & maidenhead,
339 		const std::string & msg)
340 	{
341 		if( maidenhead.empty() ) return NULL;
342 
343 		if( m_catalog.empty() ) {
344 			int nbRecs = LoadAndRegister();
345 
346 			static bool error_signaled = false ;
347 
348 			if( nbRecs <= 0 ) {
349 				LOG_WARN("Error reading Navtex stations file");
350 				if(error_signaled == false) {
351 					fl_alert("Cannot read Navtex file %s", storage_filename().first.c_str() );
352 					error_signaled = true ;
353 					return NULL;
354 				}
355 			}
356 			error_signaled = false ;
357 		}
358 
359 		const CoordinateT::Pair coo( maidenhead );
360 
361 		/// Possible Navtex stations stored by closer first.
362 		typedef std::multimap< double, CatalogType::const_iterator > SolutionType ;
363 
364 		SolutionType solDistKm;
365 
366 		double freq = freq_ll / 1000.0 ; // As kiloHertz in the data file.
367 
368 		bool okFreq = freq_acceptable( freq );
369 
370 		//LOG_INFO("Operator Maidenhead=%s lon=%lf lat=%lf okFreq=%d Origin=%c",
371 		//  maidenhead.c_str(), coo.longitude().angle(), coo.latitude().angle(), okFreq, origin );
372 
373 		for( CatalogType::const_iterator it = m_catalog.begin(), en = m_catalog.end(); it != en ; ++it )
374 		{
375 			/// The origin letters must be identical.
376 			if( origin != it->origin() ) continue ;
377 
378 			/// The two frequencies must be close more or less 10%.
379 			bool freqClose = freq_close( freq, it->frequency() );
380 			if( okFreq && ! freqClose ) continue ;
381 
382 			/// Solutions are stored smallest distance first.
383 			double dist = coo.distance( it->coordinates() );
384 			solDistKm.insert( SolutionType::value_type( dist, it ) );
385 		}
386 
387 		/// No station found.
388 		if( solDistKm.empty() ) return NULL;
389 
390 		/// Only one station, no ambiguity.
391 		SolutionType::iterator begSolKm = solDistKm.begin();
392 		if( solDistKm.size() == 1 ) return & ( *begSolKm->second );
393 
394 		SolutionType solStrDist ;
395 		// Maybe some station names appear but not others. This can be for example "Maltaradio", "Cullercoat", "Limnos" etc...
396 		for( SolutionType::iterator itSolKm = begSolKm, endSolKm = solDistKm.end(); itSolKm != endSolKm; ++itSolKm ) {
397 			std::stringstream strm ;
398 			strm << itSolKm->second->coordinates();
399 			// LOG_INFO("Name=%s Dist=%lf %s", itSolKm->second->name().c_str(), itSolKm->first, strm.str().c_str() );
400 			// The message is in uppercase anyway, so no need to convert.
401 			double str_dist = DistToStationName( msg, ucasestr( itSolKm->second->name() ) );
402 
403 			solStrDist.insert( SolutionType::value_type( str_dist, itSolKm->second ) );
404 		}
405 
406 		// There are at least two elements, so we can do this.
407 		SolutionType::iterator begSolStr = solStrDist.begin();
408 		SolutionType::iterator nxtSolStr = begSolStr;
409 		++nxtSolStr ;
410 
411 		// The first message only contains a string very similar to a radio station.
412 		if( (begSolStr->first < 2) && ( nxtSolStr->first > 2 ) ) {
413 			//LOG_INFO("Levenshtein beg=%lf beg_name=%s next=%lf next_name=%s",
414 			//  begSolStr->first, begSolStr->second->name().c_str(),
415 			//  nxtSolStr->first, nxtSolStr->second->name().c_str() );
416 			return & (*begSolStr->second) ;
417 		}
418 
419 		// There are at least two elements, and more than one station name, or none of them,
420 		// is contained in the message.
421 
422 		// Just returns the closest element.
423 		return & ( *begSolKm->second );
424 
425 		// Now we could search for a coordinate in the message, and we will keep the station which is the closest
426 		// to this coordinate. We wish to do that anyway in order to map things in KML.
427 		// Possible formats are -(This is experimental):
428 		// 67-04.0N 032-25.7E
429 		// 47-29'30N 003-16'00W
430 		// 6930.1N 01729.9E
431 		// 48-21'45N 004-31'45W
432 		// 58-37N 003-32W
433 		// 314408N 341742E
434 		// 42-42N 005-10E
435 		// 54-02.3N 004-45.8E
436 		// 55-20.76N 014-45.27E
437 		// 55-31.1 N 012-44.7 E
438 		// 5330.4N 01051.5W
439 		// 43 45.0 N - 015 44.8 E
440 		// 34-33.7N 012-28.7E
441 		// 51 10.55 N - 001 51.02 E
442 		// 51.21.67N 002.13.29E
443 		// 73 NORTH 14 EAST
444 		// 58-01.20N 005-27.08W
445 		// 50.56N 007.00,5W
446 		// 5630,1N- 00501,6E
447 		// LAT. 41.06N - LONG 012.57E
448 		// 42 40 01 N - 018 05 10 E
449 		// 40 25 31N - 18 15 30E
450 		// 40-32.2N 000-33.5E
451 		// 58-01.2 NORTH 005-27.1 WEST
452 		// 39-07,7N 026-39,2E
453 		//
454 		// ESTIMATED LIMIT OF ALL KNOWN ICE:
455 		// 4649N 5411W TO 4530N 5400W TO
456 		// 4400N 4900W TO 4545N 4530W TO
457 		// 4715N 4530W TO 5000N 4715W TO
458 		// 5530N 5115W TO 5700N 5545W.
459 		//
460 
461 
462 	}
463 }; // NavtexCatalog
464 
465 static const unsigned char code_to_ltrs[128] = {
466 	//0 1   2   3   4   5   6   7   8   9   a   b   c   d   e   f
467 	'_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', // 0
468 	'_', '_', '_', '_', '_', '_', '_', 'J', '_', '_', '_', 'F', '_', 'C', 'K', '_', // 1
469 	'_', '_', '_', '_', '_', '_', '_', 'W', '_', '_', '_', 'Y', '_', 'P', 'Q', '_', // 2
470 	'_', '_', '_', '_', '_', 'G', '_', '_', '_', 'M', 'X', '_', 'V', '_', '_', '_', // 3
471 	'_', '_', '_', '_', '_', '_', '_', 'A', '_', '_', '_', 'S', '_', 'I', 'U', '_', // 4
472 	'_', '_', '_', 'D', '_', 'R', 'E', '_', '_', 'N', '_', '_', ' ', '_', '_', '_', // 5
473 	'_', '_', '_', 'Z', '_', 'L', '_', '_', '_', 'H', '_', '_', '\n', '_', '_', '_', // 6
474 	'_', 'O', 'B', '_', 'T', '_', '_', '_', '\r', '_', '_', '_', '_', '_', '_', '_' // 7
475 };
476 
477 static const unsigned char code_to_figs[128] = {
478 	//0 1   2   3   4   5   6   7   8   9   a   b   c   d   e   f
479 	'_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', // 0
480 	'_', '_', '_', '_', '_', '_', '_', '\'', '_', '_', '_', '!', '_', ':', '(', '_', // 1
481 	'_', '_', '_', '_', '_', '_', '_', '2', '_', '_', '_', '6', '_', '0', '1', '_', // 2
482 	'_', '_', '_', '_', '_', '&', '_', '_', '_', '.', '/', '_', ';', '_', '_', '_', // 3
483 	'_', '_', '_', '_', '_', '_', '_', '-', '_', '_', '_', '\07', '_', '8', '7', '_', // 4
484 	'_', '_', '_', '$', '_', '4', '3', '_', '_', ',', '_', '_', ' ', '_', '_', '_', // 5
485 	'_', '_', '_', '"', '_', ')', '_', '_', '_', '#', '_', '_', '\n', '_', '_', '_', // 6
486 	'_', '9', '?', '_', '5', '_', '_', '_', '\r', '_', '_', '_', '_', '_', '_', '_' // 7
487 };
488 
489 static const int code_ltrs = 0x5a;
490 static const int code_figs = 0x36;
491 static const int code_alpha = 0x0f;
492 static const int code_beta = 0x33;
493 static const int code_char32 = 0x6a;
494 static const int code_rep = 0x66;
495 static const int char_bell = 0x07;
496 
497 class CCIR476 {
498 
499 	unsigned char m_ltrs_to_code[128];
500 	unsigned char m_figs_to_code[128];
501 	bool m_valid_codes[128];
502 public:
CCIR476()503 	CCIR476() {
504 		memset( m_ltrs_to_code, 0, 128 );
505 		memset( m_figs_to_code, 0, 128 );
506 		for( size_t i = 0; i < 128; ++i ) m_valid_codes[i] = false ;
507 		for (int code = 0; code < 128; code++) {
508 			// Valid codes have four bits set only. This leaves three bits for error detection.
509 			// TODO: If a code is invalid, we could take the closest value in terms of bits.
510 			if (check_bits(code)) {
511 				m_valid_codes[code] = true;
512 				unsigned char figv = code_to_figs[code];
513 				unsigned char ltrv = code_to_ltrs[code];
514 				if ( figv != '_') {
515 					m_figs_to_code[figv] = code;
516 				}
517 				if ( ltrv != '_') {
518 					m_ltrs_to_code[ltrv] = code;
519 				}
520 			}
521 		}
522 	}
523 
char_to_code(std::string & str,int ch,bool & ex_shift) const524 	void char_to_code(std::string & str, int ch, bool & ex_shift) const {
525 		ch = toupper(ch);
526 		// avoid unnecessary shifts
527 		if (ex_shift && m_figs_to_code[ch] != '\0') {
528 			str.push_back(  m_figs_to_code[ch] );
529 		}
530 		else if (!ex_shift && m_ltrs_to_code[ch] != '\0') {
531 			str.push_back( m_ltrs_to_code[ch] );
532 		}
533 		else if (m_figs_to_code[ch] != '\0') {
534 			ex_shift = true;
535 			str.push_back( code_figs );
536 			str.push_back( m_figs_to_code[ch] );
537 		}
538 		else if (m_ltrs_to_code[ch] != '\0') {
539 			ex_shift = false;
540 			str.push_back( code_ltrs );
541 			str.push_back( m_ltrs_to_code[ch] );
542 		}
543 	}
544 
code_to_char(int code,bool shift) const545 	int code_to_char(int code, bool shift) const {
546 		const unsigned char * target = (shift) ? code_to_figs : code_to_ltrs;
547 		if (target[code] != '_') {
548 			return target[code];
549 		}
550 		// default: return negated code
551 		return -code;
552 	}
553 
bytes_to_code(int * pos)554 	int bytes_to_code(int *pos) {
555 		int code = 0;
556 		int i;
557 
558 		for (i = 0; i < 7; i++)
559 			code |= ((pos[i] > 0) << i);
560 		return code;
561 	}
562 
bytes_to_char(int * pos,int shift)563 	int bytes_to_char(int *pos, int shift) {
564 		int code = bytes_to_code(pos);
565 		return code_to_char(code, shift);
566 	}
567 
568 	// http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetNaive
569 	/// Counting set bits, Brian Kernighan's way
check_bits(int v)570 	static bool check_bits(int v) {
571 		int bc = 0;
572 		while (v != 0) {
573 			bc++;
574 			v &= v - 1;
575 		}
576 		//printf("check_bits %d %d %c\n", bc, (int)code_to_ltrs[v], code_to_ltrs[v] );
577 		return bc == 4;
578 	}
579 
580 	// Is there a valid character in the next 7 ints?
valid_char_at(int * pos)581 	bool valid_char_at(int *pos) {
582 		int count = 0;
583 		int i;
584 
585 		for (i = 0; i < 7; i++)
586 			if (pos[i] > 0)
587 				count++;
588 
589 		return (count == 4);
590 	}
591 
592 };
593 
594 /// This is temporary, to manipulate a multi-line string.
595 static const char * new_line = "\n";
596 
597 // Coordinates samples:
598 // 52-08.5N 003-18.0E
599 // 51-03.93N 001-09.17E
600 // 50-40.2N 001-03.7W
601 class ccir_message : public std::string {
602 	static const size_t header_len = 10 ;
603 	static const size_t trunc_len = 5 ;
604 
605 	// Header structure is:
606 	// ZCZCabcd message text NNNN
607 	// a  : Origin of the station.
608 	// b  : Message type.
609 	// cd : Message number from this station.
610 	char m_origin ;
611 	char m_subject ;
612 	int  m_number ;
613 public:
msg_type(void) const614 	const char * msg_type(void) const
615 	{
616 		switch(m_subject) {
617 			case 'A' : return _("Navigational warning");
618 			case 'B' : return _("Meteorological warning");
619 			case 'C' : return _("Ice report");
620 			case 'D' : return _("Search & rescue information, pirate warnings");
621 			case 'E' : return _("Meteorological forecast");
622 			case 'F' : return _("Pilot service message");
623 			case 'G' : return _("AIS message");
624 			case 'H' : return _("LORAN message");
625 			case 'I' : return _("Not used");
626 			case 'J' : return _("SATNAV messages");
627 			case 'K' : return _("Other electronic navaid messages");
628 			case 'L' : return _("Navigational warnings");
629 			case 'T' : return _("Test transmissions (UK only)");
630 			case 'V' : return _("Notice to fishermen (U.S. only)");
631 			case 'W' : return _("Environmental (U.S. only)");
632 			case 'X' : return _("Special services - allocation by IMO NAVTEX Panel");
633 			case 'Y' : return _("Special services - allocation by IMO NAVTEX Panel");
634 			case 'Z' : return _("No message on hand");
635 			default  : return _("Invalid navtex subject");
636 		}
637 	}
638 private:
639 	/// Remove non-Ascii chars, replace new-line by special character etc....
cleanup()640 	void cleanup() {
641 		/// It would be possible to do the change in place, because the new string
642 		/// it shorter than the current one, but at the expense of clarity.
643 		bool wasDelim = false, wasSpace = false, chrSeen = false ;
644 		std::string newStr ;
645 		for( iterator it = begin(); it != end(); ++it ) {
646 			switch( *it ) {
647 				case '\n':
648 				case '\r': wasDelim = true ;
649 					   break ;
650 				case ' ' :
651 				case '\t': wasSpace = true ;
652 					   break ;
653 				default  : if( chrSeen ) {
654 						   if( wasDelim ) {
655 							  newStr.append(new_line);
656 						   } else if( wasSpace ) {
657 							  newStr.push_back(' ');
658 						}
659 					   }
660 					   wasDelim = false ;
661 					   wasSpace = false ;
662 					   chrSeen = true ;
663 					   newStr.push_back( *it );
664 			}
665 		}
666 		swap( newStr );
667 	}
668 
init_members()669 	void init_members() {
670 		m_origin = '?';
671 		m_subject = '?';
672 		m_number = 0 ;
673 	}
674 public:
ccir_message()675 	ccir_message() {
676 		init_members();
677 	}
678 
ccir_message(const std::string & s,char origin,char subject,int number)679 	ccir_message( const std::string & s, char origin, char subject, int number )
680 	: std::string(s)
681 	, m_origin(origin)
682 	, m_subject(subject)
683 	, m_number(number) {
684 		cleanup();
685 	}
686 
reset_msg()687 	void reset_msg() {
688 		clear();
689 		init_members();
690 	}
691 
692 	typedef std::pair<bool, ccir_message> detect_result ;
detect_header()693 	detect_result detect_header() {
694 		size_t qlen = size();
695 
696 		if (qlen >= header_len) {
697 			const char * comp = & (*this)[ qlen - header_len ];
698 			if(
699 				(comp[0] == 'Z') &&
700 				(comp[1] == 'C') &&
701 				(comp[2] == 'Z') &&
702 				(comp[3] == 'C') &&
703 				(comp[4] == ' ') &&
704 				isalnum(comp[5]) &&
705 				isalnum(comp[6]) &&
706 				isdigit(comp[7]) &&
707 				isdigit(comp[8]) &&
708 				// (comp[9] == '\r') )
709 				(strchr( "\n\r", comp[9] ) ) ) {
710 
711 				/// This returns the garbage before the valid header.
712 				// Garbage because the trailer could not be read, but maybe header OK.
713 				ccir_message msg_cut(
714 					substr( 0, size() - header_len ),
715 						m_origin,
716 					m_subject,
717 					m_number );
718 				m_origin  = comp[5];
719 				m_subject = comp[6];
720 				m_number = ( comp[7] - '0' ) * 10 + ( comp[8] - '0' );
721 				// Remove the beginning useless chars.
722 				/// TODO: Read broken headers such as "ZCZC EA0?"
723 				clear();
724 				return detect_result( true, msg_cut );
725 			}
726 		}
727 		return detect_result( false, ccir_message() ); ;
728 	}
729 
detect_end()730 	bool detect_end() {
731 		// Should be "\r\nNNNN\r\n" theoretically, but tolerates shorter strings.
732 		static const size_t slen = 4 ;
733 		static const char stop_valid[slen + 1] = "NNNN";
734 		size_t qlen = size();
735 		if (qlen < slen) {
736 			return false;
737 		}
738 		std::string comp = substr(qlen - slen, slen);
739 		bool end_seen = comp == stop_valid;
740 		if( end_seen ) {
741 			erase( qlen - slen, slen );
742 			LOG_INFO("\n%s", c_str());
743 		}
744 		return end_seen ;
745 	}
746 
display(const std::string & alt_string)747 	void display( const std::string & alt_string ) {
748 		std::string::operator=( alt_string );
749 		cleanup();
750 
751 		long long currFreq = wf->rfcarrier();
752 
753 		if( ! progdefaults.NVTX_AdifLog && ! progdefaults.NVTX_KmlLog ) {
754 			return ;
755 		}
756 
757 		const NavtexRecord * ptrNavRec = NavtexCatalog::InstCatalog().FindStation(currFreq, m_origin, progdefaults.myLocator, *this );
758 		if( ptrNavRec != NULL ) {
759 			LOG_INFO("Locator=%s Origin=%c freq=%d name=%s lon=%lf lat=%lf",
760 				progdefaults.myLocator.c_str(),
761 				m_origin,
762 				static_cast<int>(currFreq),
763 				ptrNavRec->name().c_str(),
764 				ptrNavRec->coordinates().longitude().angle(),
765 				ptrNavRec->coordinates().latitude().angle() );
766 		} else {
767 			LOG_INFO("Locator=%s Origin=%c freq=%d Navtex station not found",
768 				progdefaults.myLocator.c_str(),
769 				m_origin,
770 				static_cast<int>(currFreq) );
771 		}
772 
773 		if( progdefaults.NVTX_AdifLog ) {
774 			/// For updating the logbook with received messages.
775 			QsoHelper qso(MODE_NAVTEX);
776 
777 			if( ptrNavRec ) {
778 				qso.Push(QTH, ptrNavRec->country() );
779 				qso.Push(CALL, ptrNavRec->callsign() );
780 				qso.Push(COUNTRY, ptrNavRec->country() );
781 				qso.Push(GRIDSQUARE, ptrNavRec->coordinates().locator() );
782 				qso.Push(NAME, ptrNavRec->name() );
783 				/// If the header is clean, the message type is removed from the string.
784 				// In this context, this field cannot be used.
785 				qso.Push(XCHG1, msg_type() );
786 				qso.Push(SRX, strformat( "%d", m_number ) );
787 			} else {
788 				qso.Push(NAME, std::string("Station_") + m_origin );
789 			}
790 
791 			// Sequence of Chars and line-breaks, ASCII CR (code 13) + ASCII LF (code 10)
792 			qso.Push(NOTES, strreplace( *this, new_line, ADIF_EOL ) );
793 		}
794 
795 		// Adds a placemark to the navtex KML file.
796 		if( progdefaults.NVTX_KmlLog ) {
797 			if( ptrNavRec ) {
798 				KmlServer::CustomDataT custData ;
799 				custData.Push( "Callsign", ptrNavRec->callsign() );
800 				custData.Push( "Country", ptrNavRec->country() );
801 				custData.Push( "Locator", ptrNavRec->coordinates().locator() );
802 				custData.Push( "Message number", m_number );
803 				custData.Push( "Frequency", currFreq );
804 
805 				custData.Push( "Mode", mode_info[MODE_NAVTEX].adif_name );
806 				custData.Push( "Message", *this );
807 
808 				KmlServer::GetInstance()->Broadcast(
809 					"Navtex",
810 					0,
811 					ptrNavRec->coordinates(),
812 					0,
813 					ptrNavRec->name(),
814 					"navtex_station",
815 					substr( 0, 20 ) + "...",
816 					custData );
817 			}
818 
819 			// TODO: Parse the message to extract coordinates.
820 		}
821 	} // display
822 }; // ccir_message
823 
824 static const int deviation_f = 85;
825 
826 static const double dflt_center_freq = 1000.0 ;
827 
828 class navtex ;
829 
830 /// Implements PIMPL idiom.
831 class navtex_implementation {
832 
833 	enum State {
834 		SYNC_SETUP, SYNC, READ_DATA
835 	};
836 
state_to_str(State s)837 	static const char * state_to_str( State s ) {
838 		switch( s ) {
839 			case SYNC_SETUP: return "SYNC_SETUP";
840 			case SYNC   : return "SYNC";
841 			case READ_DATA : return "READ_DATA";
842 			default     : return "Unknown" ;
843 		}
844 	}
845 
846 	bool                            m_only_sitor_b ;
847 	int                             m_message_counter ;
848 
849 	static const size_t             m_tx_block_len = 1024 ;
850 	/// Between -1 and 1.
851 	double                          m_tx_buf[m_tx_block_len];
852 	size_t                          m_tx_counter ;
853 
854 	navtex                        * m_ptr_navtex ;
855 
856 	pthread_mutex_t                 m_mutex_tx ;
857 	typedef std::list<std::string>  TxMsgQueueT ;
858 	TxMsgQueueT                     m_tx_msg_queue ;
859 
860 	double                          m_metric ;
861 
862 	CCIR476             m_ccir476;
863 	typedef std::list<int> sync_chrs_type ;
864 	ccir_message                    m_curr_msg ;
865 
866 	double               m_message_time ;
867 	double               m_early_accumulator ;
868 	double               m_prompt_accumulator ;
869 	double               m_late_accumulator ;
870 	double               m_mark_f, m_space_f;
871 	double               m_time_sec;
872 
873 	double               m_baud_rate ;
874 	int                 m_sample_rate ;
875 	int			m_averaged_mark_state;
876 	int                 m_bit_duration ;
877 	fftfilt					*m_mark_lowpass;
878 	fftfilt					*m_space_lowpass;
879 	double					m_mark_phase;
880 	double					m_space_phase;
881 	double                  m_bit_sample_count;
882 	State                 m_state;
883 	int                 m_sample_count;
884 	double                  m_next_early_event;
885 	double                  m_next_prompt_event;
886 	double                  m_next_late_event;
887 	double                  m_average_early_signal;
888 	double                  m_average_prompt_signal;
889 	double                  m_average_late_signal;
890 	std::vector<int>	m_bit_values;
891 	int			m_bit_cursor;
892 	bool                   m_shift ;
893 	bool                   m_pulse_edge_event;
894 	int                 m_error_count;
895 	bool                   m_alpha_phase ;
896 	bool                   m_header_found ;
897 	char snrmsg[80];
898 	// filter method related
899 	double               m_center_frequency_f ;
900 
901 	navtex_implementation( const navtex_implementation & );
902 	navtex_implementation();
903 	navtex_implementation & operator=( const navtex_implementation & );
904 public:
navtex_implementation(int the_sample_rate,bool only_sitor_b,navtex * ptr_navtex)905 	navtex_implementation(int the_sample_rate, bool only_sitor_b, navtex * ptr_navtex ) {
906 		pthread_mutex_init( &m_mutex_tx, NULL );
907 		m_ptr_navtex = ptr_navtex ;
908 		m_only_sitor_b = only_sitor_b ;
909 		m_message_counter = 1 ;
910 		m_metric = 0.0 ;
911 		m_time_sec = 0.0 ;
912 		m_state = SYNC_SETUP;
913 		m_message_time = 0.0 ;
914 		m_early_accumulator = 0;
915 		m_prompt_accumulator = 0;
916 		m_late_accumulator = 0;
917 		m_sample_rate = the_sample_rate;
918 		m_bit_duration = 0;
919 		m_shift = false;
920 		m_alpha_phase = false;
921 		m_header_found = false;
922 		m_center_frequency_f = dflt_center_freq;
923 		// this value must never be zero and bigger than 10.
924 		m_baud_rate = 100;
925 		double m_bit_duration_seconds = 1.0 / m_baud_rate;
926 		m_bit_sample_count = m_sample_rate * m_bit_duration_seconds;
927 		// A narrower spread between signals allows the modem to
928 		// center on the pulses better, but a wider spread makes
929 		// more robust under noisy conditions. 1/5 seems to work.
930 		m_next_early_event = 0;
931 		m_next_prompt_event = m_bit_sample_count / 5;
932 		m_next_late_event = m_bit_sample_count * 2 / 5;
933 		m_average_early_signal = 0;
934 		m_average_prompt_signal = 0;
935 		m_average_late_signal = 0;
936 		m_error_count = 0;
937 		m_sample_count = 0;
938 		// keep 1 second worth of bit values for decoding
939 		m_bit_values.resize(m_baud_rate);
940 		m_bit_cursor = 0;
941 
942 		m_mark_lowpass = 0;
943 		m_space_lowpass = 0;
944 
945 		set_filter_values();
946 		configure_filters();
947 	}
~navtex_implementation()948 	~navtex_implementation() {
949 		pthread_mutex_destroy( &m_mutex_tx );
950 	}
951 private:
952 
set_filter_values()953 	void set_filter_values() {
954 		m_mark_f = m_center_frequency_f + deviation_f;
955 		m_space_f = m_center_frequency_f - deviation_f;
956 		m_mark_phase = 0;
957 		m_space_phase = 0;
958 	}
959 
configure_filters()960 	void configure_filters() {
961 // do not allow filters to be changed during signal processing!
962 		guard_lock filter_guard(&navtex_filter_mutex);
963 
964 		const int filtlen = 512;
965 		if (m_mark_lowpass) delete m_mark_lowpass;
966 		m_mark_lowpass = new fftfilt(m_baud_rate/m_sample_rate, filtlen);
967 		m_mark_lowpass->rtty_filter(m_baud_rate/m_sample_rate);
968 
969 		if (m_space_lowpass) delete m_space_lowpass;
970 		m_space_lowpass = new fftfilt(m_baud_rate/m_sample_rate, filtlen);
971 		m_space_lowpass->rtty_filter(m_baud_rate/m_sample_rate);
972 	}
973 
set_state(State s)974 	void set_state(State s) {
975 		if (s != m_state) {
976 			m_state = s;
977 		set_label_from_state();
978 		}
979 	}
980 
981 	/// The parameter is appended at the message end.
flush_message(const std::string & extra_info)982 	void flush_message(const std::string & extra_info)
983 	{
984 		if( m_header_found )
985 		{
986 			m_header_found = false;
987 			display_message( m_curr_msg, m_curr_msg + extra_info );
988 		}
989 		else
990 		{
991 			display_message( m_curr_msg, "[Lost header]:" + m_curr_msg + extra_info );
992 		}
993 		m_curr_msg.reset_msg();
994 		m_message_time = m_time_sec;
995 	}
996 
997 	/// Checks that we have no waited too long, and if so, flushes the message with a specific terminator.
process_timeout()998 	void process_timeout() {
999 		/// No messaging in SitorB
1000 		if ( m_only_sitor_b ) {
1001 			return ;
1002 		}
1003 		bool timeOut = m_time_sec - m_message_time > 600 ;
1004 		if ( ! timeOut ) return ;
1005 		LOG_INFO("Timeout: time_sec=%lf, message_time=%lf", m_time_sec, m_message_time );
1006 
1007 		// TODO: Headerless messages could be dropped if shorter than X chars.
1008 		flush_message(":<TIMEOUT>");
1009 	}
1010 
process_messages(int c)1011 	void process_messages(int c) {
1012 		m_curr_msg.push_back((char) c);
1013 
1014 		/// No header nor trailer for plain SitorB.
1015 		if ( m_only_sitor_b ) {
1016 			m_header_found = true;
1017 			m_message_time = m_time_sec;
1018 			return;
1019 		}
1020 
1021 		ccir_message::detect_result msg_cut = m_curr_msg.detect_header();
1022 		if ( msg_cut.first ) {
1023 			/// Maybe the message was already valid.
1024 			if( m_header_found )
1025 			{
1026 				display_message( msg_cut.second, msg_cut.second + ":[Lost trailer]" );
1027 			}
1028 			else
1029 			{
1030 				/// Maybe only non-significant chars.
1031 				if( ! msg_cut.second.empty() )
1032 				{
1033 					display_message( msg_cut.second, "[Lost header]:" + msg_cut.second + ":[Lost trailer]" );
1034 				}
1035 			}
1036 			m_header_found = true;
1037 			m_message_time = m_time_sec;
1038 
1039 		} else { // valid message state
1040 			if ( m_curr_msg.detect_end() ) {
1041 				flush_message("");
1042 			}
1043 		}
1044 	}
1045 
1046 	// The rep character is transmitted 5 characters (35 bits) ahead of
1047 	// the alpha character.
fec_offset(int offset)1048 	int fec_offset(int offset) {
1049 		return offset - 35;
1050 	}
1051 
1052 	// Flip the sign of the smallest (least certain) bit in a character;
1053 	// hopefully this will result in the right valid character.
flip_smallest_bit(int * pos)1054 	void flip_smallest_bit(int *pos) {
1055 		int min_zero = INT_MIN, min_one = INT_MAX;
1056 		int min_zero_pos = -1, min_one_pos = -1;
1057 		int count_zero = 0, count_one = 1;
1058 		int val, i;
1059 
1060 		for (i = 0; i < 7; i++) {
1061 			val = pos[i];
1062 			if (val < 0) {
1063 				count_zero++;
1064 				if (val > min_zero) {
1065 					min_zero = val;
1066 					min_zero_pos = i;
1067 				}
1068 			} else {
1069 				count_one++;
1070 				if (val < min_one) {
1071 					min_one = val;
1072 					min_one_pos = i;
1073 				}
1074 			}
1075 		}
1076 
1077 		// A valid character has 3 zeroes and 4 ones, if we have
1078 		// 5 ones or 4 zeroes, flipping the smallest one would make
1079 		// this character valid.
1080 		if (count_zero == 4)
1081 			pos[min_zero_pos] = -pos[min_zero_pos];
1082 		else if (count_one == 5)
1083 			pos[min_one_pos] = -pos[min_one_pos];
1084 	}
1085 
1086 	// Try to find a position in the bit stream with:
1087 	// - the largest number of valid characters, and
1088 	// - with rep (duplicate) characters in the right locations
1089 	// This way the code can sync up with an incoming signal after
1090 	// the initial alpha/rep synchronisation
1091 	//
1092 	// http://www.arachnoid.com/JNX/index.html
1093 	// "NAUTICAL" becomes:
1094 	// rep alpha rep alpha N alpha A alpha U N T A I U C T A I L C blank A blank L
find_alpha_characters(void)1095 	int find_alpha_characters(void) {
1096 		int best_offset = 0;
1097 		int best_score = 0;
1098 		int offset, i;
1099 
1100 		// With 7 bits per character, and interleaved rep & alpha
1101 		// characters, the first alpha character with a corresponding
1102 		// rep in the stream can be in any of 14 locations
1103 		for (offset = 35; offset < (35 + 14); offset++) {
1104 			int score = 0;
1105 			int reps = 0;
1106 			int limit = m_bit_values.size() - 7;
1107 
1108 			// Search for the largest sequence of valid characters
1109 			for (i = offset; i < limit; i += 7) {
1110 				if (m_ccir476.valid_char_at(&m_bit_values[i])) {
1111 					int ri = fec_offset(i);
1112 					int code = m_ccir476.bytes_to_code(&m_bit_values[i]);
1113 					int rep = m_ccir476.bytes_to_code(&m_bit_values[ri]);
1114 
1115 					// This character is valid
1116 					score++;
1117 
1118 					// Does it match its rep?
1119 					if (code == rep) {
1120 						// This offset is wrong, rep
1121 						// and alpha are spaced odd
1122 						if (code == code_alpha ||
1123 						    code == code_rep) {
1124 							score = 0;
1125 							continue;
1126 						}
1127 						reps++;
1128 					} else if (code == code_alpha) {
1129 						// Is there a matching rep to
1130 						// this alpha?
1131 						int ri = i - 7;
1132 						int rep = m_ccir476.bytes_to_code(&m_bit_values[ri]);
1133 						if (rep == code_rep) {
1134 							reps++;
1135 						}
1136 					}
1137 				}
1138 			}
1139 
1140 			// the most valid characters, with at least 3 FEC reps
1141 			if (reps >= 3 && score + reps > best_score) {
1142 				best_score = score + reps;
1143 				best_offset = offset;
1144 			}
1145 		}
1146 
1147 		// m_bit_values fits 14 characters; if there are at least
1148 		// 9 good ones, tell the caller where they start
1149 		if (best_score > 8)
1150 			return best_offset;
1151 		else
1152 			return -1;
1153 	}
1154 
1155 	// Turns accumulator values (estimates of whether a bit is 1 or 0)
1156 	// into navtex messages
handle_bit_value(int accumulator)1157 	void handle_bit_value(int accumulator) {
1158 		int buffersize = m_bit_values.size();
1159 		int i, offset = 0;
1160 
1161 		// Store the received value in the bit stream
1162 		for (i = 0; i < buffersize - 1; i++) {
1163 			m_bit_values[i] = m_bit_values[i+1];
1164 		}
1165 		m_bit_values[buffersize - 1] = accumulator;
1166 		if (m_bit_cursor > 0)
1167 			m_bit_cursor--;
1168 
1169 		// Find the most likely location where the message starts
1170 		if (m_state == SYNC) {
1171 			offset = find_alpha_characters();
1172 			if (offset >= 0) {
1173 				set_state(READ_DATA);
1174 				m_bit_cursor = offset;
1175 				m_alpha_phase = true;
1176 			} else
1177 				set_state(SYNC_SETUP);
1178 		}
1179 
1180 		// Process 7-bit characters as they come in,
1181 		// skipping rep (duplicate) characters
1182 		if (m_state == READ_DATA) {
1183 			if (m_bit_cursor < buffersize - 7) {
1184 				if (m_alpha_phase) {
1185 					int ret = process_bytes(m_bit_cursor);
1186 					m_error_count -= ret;
1187 					if (m_error_count > 5)
1188 						set_state(SYNC_SETUP);
1189 					if (m_error_count < 0)
1190 						m_error_count = 0;
1191 				}
1192 				m_alpha_phase = !m_alpha_phase;
1193 				m_bit_cursor += 7;
1194 			}
1195 		}
1196 	}
1197 
1198 	// Turn a series of 7 bit confidence values into a character
1199 	//
1200 	// 1 on successful decode of the alpha character
1201 	// 0 on unmodified FEC replacement
1202 	// -1 on soft failure (FEC calculation)
1203 	// -2 on hard failure
process_bytes(int m_bit_cursor)1204 	int process_bytes(int m_bit_cursor) {
1205 		int code = m_ccir476.bytes_to_code(&m_bit_values[m_bit_cursor]);
1206 		int success = 0;
1207 
1208 		if (m_ccir476.check_bits(code)) {
1209 			LOG_DEBUG("valid code : %x (%c)", code, m_ccir476.code_to_char(code, m_shift));
1210 			success = 1;
1211 			goto decode;
1212 		}
1213 
1214 		if (fec_offset(m_bit_cursor) < 0)
1215 			return -1;
1216 
1217 		// The alpha (primary) character received was not correct.
1218 		// Try the rep (duplicate) copy of the character, and some
1219 		// permutations to see if the correct character can be found.
1220 		{
1221 			int i, calc, avg[7];
1222 			// Rep is 5 characters before alpha.
1223 			int reppos = fec_offset(m_bit_cursor);
1224 			int rep = m_ccir476.bytes_to_code(&m_bit_values[reppos]);
1225 			if (CCIR476::check_bits(rep)) {
1226 				// Current code is probably code_alpha.
1227 				// Skip decoding to avoid switching phase.
1228 				if (rep == code_rep)
1229 					return 0;
1230 				LOG_DEBUG("FEC replacement: %x -> %x (%c)", code, rep, m_ccir476.code_to_char(rep, m_shift));
1231 				code = rep;
1232 				goto decode;
1233 			}
1234 
1235 			// Neither alpha or rep are valid. Check whether
1236 			// the average of the two is a valid character.
1237 			for (i = 0; i < 7; i++) {
1238 				int a = m_bit_values[m_bit_cursor + i];
1239 				int r = m_bit_values[reppos + i];
1240 				avg[i] = a + r;
1241 			}
1242 
1243 			calc = m_ccir476.bytes_to_code(avg);
1244 			if (CCIR476::check_bits(calc)) {
1245 				LOG_DEBUG("FEC calculation: %x & %x -> %x (%c)", code, rep, calc, m_ccir476.code_to_char(calc, m_shift));
1246 				code = calc;
1247 				success = -1;
1248 				goto decode;
1249 			}
1250 
1251 			// Flip the lowest confidence bit in alpha.
1252 			flip_smallest_bit(&m_bit_values[m_bit_cursor]);
1253 			calc = m_ccir476.bytes_to_code(&m_bit_values[m_bit_cursor]);
1254 			if (CCIR476::check_bits(calc)) {
1255 				LOG_DEBUG("FEC calculation: %x & %x -> %x (%c)", code, rep, calc, m_ccir476.code_to_char(calc, m_shift));
1256 				code = calc;
1257 				success = -1;
1258 				goto decode;
1259 			}
1260 
1261 			// Flip the lowest confidence bit in rep.
1262 			flip_smallest_bit(&m_bit_values[reppos]);
1263 			calc = m_ccir476.bytes_to_code(&m_bit_values[reppos]);
1264 			if (CCIR476::check_bits(calc)) {
1265 				LOG_DEBUG("FEC calculation: %x & %x -> %x (%c)", code, rep, calc, m_ccir476.code_to_char(calc, m_shift));
1266 				code = calc;
1267 				success = -1;
1268 				goto decode;
1269 			}
1270 
1271 			// Try flipping the bit with the lowest confidence
1272 			// in the average of alpha & rep.
1273 			flip_smallest_bit(avg);
1274 			calc = m_ccir476.bytes_to_code(avg);
1275 			if (CCIR476::check_bits(calc)) {
1276 				LOG_DEBUG("FEC calculation: %x & %x -> %x (%c)", code, rep, calc, m_ccir476.code_to_char(calc, m_shift));
1277 				code = calc;
1278 				success = -1;
1279 				goto decode;
1280 			}
1281 
1282 			LOG_DEBUG("decode fail %x, %x", code, rep);
1283 			return -2;
1284 		}
1285 
1286 	decode:
1287 		process_char(code);
1288 		return success;
1289 	}
1290 
process_char(int chr)1291 	bool process_char(int chr) {
1292 		static int last_char = 0;
1293 		switch (chr) {
1294 			case code_rep:
1295 				// This code should run in alpha phase, but
1296 				// it just received two rep characters. Fix
1297 				// the rep/alpha phase, so FEC works again.
1298 				if (last_char == code_rep) {
1299 					LOG_DEBUG("fixing rep/alpha sync");
1300 					m_alpha_phase = false;
1301 				}
1302 				break;
1303 			case code_alpha:
1304 				break;
1305 			case code_beta:
1306 				break;
1307 			case code_char32:
1308 				break;
1309 			case code_ltrs:
1310 				m_shift = false;
1311 				break;
1312 			case code_figs:
1313 				m_shift = true;
1314 				break;
1315 			default:
1316 				chr = m_ccir476.code_to_char(chr, m_shift);
1317 				if (chr < 0) {
1318 					LOG_INFO(_("Missed this code: %x"), abs(chr));
1319 				} else {
1320 					filter_print(chr);
1321 					process_messages(chr);
1322 				}
1323 				break;
1324 			} // switch
1325 
1326 		last_char = chr;
1327 		return true;
1328 	}
1329 
filter_print(int c)1330 	void filter_print(int c) {
1331 		if (c == char_bell) {
1332 			/// TODO: It should be a beep, but French navtex displays a quote.
1333 			put_rx_char('\'');
1334 		} else if (c != -1 && c != '\r' && c != code_alpha && c != code_rep) {
1335 			put_rx_char(c);
1336 		}
1337 	}
1338 
compute_metric(void)1339 	void compute_metric(void)
1340 	{
1341 		static double sigpwr = 0.0 ;
1342 		static double noisepwr = 0.0;
1343 		double delta = m_baud_rate/8.0;
1344 		double np = wf->powerDensity(m_center_frequency_f, delta) * 3000 / delta;
1345 		double sp =
1346 			wf->powerDensity(m_mark_f, delta) +
1347 				wf->powerDensity(m_space_f, delta) + 1e-10;
1348 		double snr;
1349 
1350 		sigpwr = decayavg ( sigpwr, sp, sp > sigpwr ? 2 : 8);
1351 		noisepwr = decayavg ( noisepwr, np, 16 );
1352 		snr = 10*log10(sigpwr / noisepwr);
1353 
1354 		snprintf(snrmsg, sizeof(snrmsg), "s/n %3.0f dB", snr);
1355 		put_Status2(snrmsg);
1356 		m_metric = CLAMP((3000 / delta) * (sigpwr/noisepwr), 0.0, 100.0);
1357 		m_ptr_navtex->display_metric(m_metric);
1358 
1359 	}
1360 
process_afc()1361 	void process_afc() {
1362 		if( progStatus.afconoff == false ) return ;
1363 		static size_t cnt_upd = 0 ;
1364 		static const size_t delay_upd = 50 ;
1365 		++cnt_upd ;
1366 
1367 		/// AFC from time to time.
1368 		if( ( cnt_upd % delay_upd ) != 0 ) {
1369 			return ;
1370 		}
1371 		static int cnt_read_data = 0 ;
1372 		/// This centers the carrier where the activity is the strongest.
1373 		static const int bw[][2] = {
1374 			{ -deviation_f - 10, -deviation_f + 5 },
1375 			{  deviation_f - 5,  deviation_f + 10 } };
1376 // find mid frequency of power spectra in mark/space frequency interval
1377 		double max_carrier = wf->powerDensityMaximum( 2, bw );
1378 
1379 		/// Do not change the frequency too quickly if an image is received.
1380 		double next_carr = 0.0 ;
1381 
1382 		State lingering_state ;
1383 		if( m_state == READ_DATA ) {
1384 			/// Proportional to the number of lines between each AFC update.
1385 			cnt_read_data = delay_upd / 20 ;
1386 			lingering_state = READ_DATA ;
1387 		} else {
1388 			if( cnt_read_data ) {
1389 				--cnt_read_data ;
1390 				lingering_state = READ_DATA ;
1391 			} else {
1392 				lingering_state = m_state ;
1393 				/// Maybe this is the phasing signal, so we recenter.
1394 				double pwr_left = wf->powerDensity ( max_carrier - deviation_f, 30 );
1395 				double pwr_right = wf->powerDensity( max_carrier + deviation_f, 30 );
1396 				static const double ratio_left_right = 5.0 ;
1397 				if( pwr_left > ratio_left_right * pwr_right ) {
1398 					max_carrier -= deviation_f ;
1399 				} else if ( ratio_left_right * pwr_left < pwr_right ) {
1400 					max_carrier += deviation_f ;
1401 				}
1402 			}
1403 		}
1404 		switch( lingering_state ) {
1405 			case SYNC_SETUP:
1406 				next_carr = max_carrier ;
1407 				break;
1408 			case SYNC:
1409 				next_carr = decayavg( m_center_frequency_f, max_carrier, 1 );
1410 				break;
1411 			case READ_DATA:
1412 				// It will stay stable for a couple of calls.
1413 				if( max_carrier < m_center_frequency_f )
1414 					next_carr = std::max( max_carrier, m_center_frequency_f - 3.0 );
1415 				else if( max_carrier > m_center_frequency_f )
1416 					next_carr = std::min( max_carrier, m_center_frequency_f + 3.0 );
1417 				else next_carr = max_carrier ;
1418 				break;
1419 			default:
1420 				LOG_ERROR("Should not happen: lingering_state=%d", (int)lingering_state );
1421 				break ;
1422 		}
1423 
1424 		LOG_DEBUG("m_center_frequency_f=%f max_carrier=%f next_carr=%f cnt_read_data=%d",
1425 			(double)m_center_frequency_f, max_carrier, next_carr, cnt_read_data );
1426 		double delta = fabs( m_center_frequency_f - next_carr );
1427 		if( delta > 1.0 ) { // Hertz.
1428 			m_ptr_navtex->set_freq(next_carr);
1429 		}
1430 	}
1431 
1432 	// The signal is sampled at three points: early, prompt, and late.
1433 	// The prompt event is where the signal is decoded, while early and
1434 	// late are only used to adjust the time of the sampling to match
1435 	// the incoming signal.
1436 	//
1437 	// The early event happens 1/5 bit period before the prompt event,
1438 	// and the late event 1/5 bit period later. If the incoming signal
1439 	// peaks early, it means the decoder is late. That is, if the early
1440 	// signal is "too large", decoding should to happen earlier.
1441 	//
1442 	// Attempt to center the signal so the accumulator is at its
1443 	// maximum deviation at the prompt event. If the bit is decoded
1444 	// too early or too late, the code is more sensitive to noise,
1445 	// and less likely to decode the signal correctly.
process_multicorrelator()1446 	void process_multicorrelator() {
1447 		// Adjust the sampling period once every 8 bit periods.
1448 		if (m_sample_count % (int)(m_bit_sample_count * 8))
1449 			return;
1450 
1451 		// Calculate the slope between early and late signals
1452 		// to align the logic sampling with the received signal
1453 		double slope = m_average_late_signal - m_average_early_signal;
1454 
1455 		if (m_average_prompt_signal * 1.05 < m_average_early_signal &&
1456 		    m_average_prompt_signal * 1.05 < m_average_late_signal) {
1457 			// At a signal minimum. Get out quickly.
1458 			if (m_average_early_signal > m_average_late_signal) {
1459 				// move prompt to where early is
1460 				slope = m_next_early_event - m_next_prompt_event;
1461 				slope = fmod(slope - m_bit_sample_count, m_bit_sample_count);
1462 				m_average_late_signal = m_average_prompt_signal;
1463 				m_average_prompt_signal = m_average_early_signal;
1464 			} else {
1465 				// move prompt to where late is
1466 				slope = m_next_late_event - m_next_prompt_event;
1467 				slope = fmod(slope + m_bit_sample_count, m_bit_sample_count);
1468 				m_average_early_signal = m_average_prompt_signal;
1469 				m_average_prompt_signal = m_average_late_signal;
1470 			}
1471 		} else
1472 			slope /= 1024;
1473 
1474 		if (slope) {
1475 			m_next_early_event += slope;
1476 			m_next_prompt_event += slope;
1477 			m_next_late_event += slope;
1478 			LOG_DEBUG("adjusting by %1.2f, early %1.1f, prompt %1.1f, late %1.1f", slope, m_average_early_signal, m_average_prompt_signal, m_average_late_signal);
1479 		}
1480 	}
1481 
1482 	/* A NAVTEX message is built on SITOR collective B-mode and consists of:
1483 	* a phasing signal of at least ten seconds
1484 	* the four characters "ZCZC" that identify the end of phasing
1485 	* a single space
1486 	* four characters B1, B2, B3 and B4:
1487 		* B1 is an alpha character identifying the station,
1488 	* B2 is an alpha character used to identify the subject of the message.
1489 		* B3 and B4 are two-digit numerics identifying individual messages
1490 	* a carriage return and a line feed
1491 	* the information
1492 	* the four characters "NNNN" to identify the end of information
1493 	* a carriage return and two line feeds
1494 	* either
1495 		* 5 or more seconds of phasing signal and another message starting with "ZCZC" or
1496 		* an end of emission idle signal alpha for at least 2 seconds.  */
1497 public:
process_data(const double * data,int nb_samples)1498 	void process_data(const double * data, int nb_samples) {
1499 			cmplx z, zmark, zspace, *zp_mark, *zp_space;
1500 
1501 		process_afc();
1502 		process_timeout();
1503 // prevent user waterfall interaction from changing filters!
1504 		guard_lock g( &navtex_filter_mutex );
1505 
1506 		for( int i =0; i < nb_samples; ++i ) {
1507 			int n_out;
1508 
1509 			m_time_sec = m_sample_count / m_sample_rate ;
1510 
1511 			double dv = 32767 * data[i];
1512 			z = cmplx(dv, dv);
1513 
1514 			zmark = mixer(m_mark_phase, m_mark_f, z);
1515 			m_mark_lowpass->run(zmark, &zp_mark);
1516 
1517 			zspace = mixer(m_space_phase, m_space_f, z);
1518 			n_out = m_space_lowpass->run(zspace, &zp_space);
1519 
1520 			if (n_out)
1521 				process_fft_output(zp_mark, zp_space, n_out);
1522 		}
1523 	}
1524 
1525 private:
mixer(double & phase,double f,cmplx in)1526 	cmplx mixer(double &phase, double f, cmplx in)
1527 	{
1528 		cmplx z = cmplx( cos(phase), sin(phase)) * in;
1529 
1530 		phase -= TWOPI * f / m_sample_rate;
1531 		if (phase < -TWOPI) phase += TWOPI;
1532 
1533 		return z;
1534 	}
1535 
1536 	// noise average decays fast down, slow up
noise_decay(double avg,double value)1537 	double noise_decay(double avg, double value) {
1538 		int divisor;
1539 		if (value < avg)
1540 			divisor = m_bit_sample_count / 4;
1541 		else
1542 			divisor = m_bit_sample_count * 48;
1543 		return decayavg(avg, value, divisor);
1544 	}
1545 
1546 	// envelope average decays fast up, slow down
envelope_decay(double avg,double value)1547 	double envelope_decay(double avg, double value) {
1548 		int divisor;
1549 		if (value > avg)
1550 			divisor = m_bit_sample_count / 4;
1551 		else
1552 			divisor = m_bit_sample_count * 16;
1553 		return decayavg(avg, value, divisor);
1554 	}
1555 
process_fft_output(cmplx * zp_mark,cmplx * zp_space,int samples)1556 	void process_fft_output(cmplx *zp_mark, cmplx *zp_space, int samples) {
1557 		// envelope & noise levels for mark & space, respectively
1558 		static double mark_env = 0, space_env = 0;
1559 		static double mark_noise = 0, space_noise = 0;
1560 
1561 		for (int i = 0; i < samples; i++) {
1562 			double mark_abs = abs(zp_mark[i]);
1563 			double space_abs = abs(zp_space[i]);
1564 
1565 			process_multicorrelator();
1566 
1567 			// determine noise floor & envelope for mark & space
1568 			mark_env = envelope_decay(mark_env, mark_abs);
1569 			mark_noise = noise_decay(mark_noise, mark_abs);
1570 
1571 			space_env = envelope_decay(space_env, space_abs);
1572 			space_noise = noise_decay(space_noise, space_abs);
1573 
1574 			double noise_floor = (space_noise + mark_noise) / 2;
1575 
1576 			// clip mark & space to envelope & floor
1577 			mark_abs = min(mark_abs, mark_env);
1578 			mark_abs = max(mark_abs, noise_floor);
1579 
1580 			space_abs = min(space_abs, space_env);
1581 			space_abs = max(space_abs, noise_floor);
1582 
1583 			// mark-space discriminator with automatic threshold
1584 			// correction, see:
1585 			// http://www.w7ay.net/site/Technical/ATC/
1586 			double logic_level =
1587 				(mark_abs - noise_floor) * (mark_env - noise_floor) -
1588 				(space_abs - noise_floor) * (space_env - noise_floor) -
1589 				0.5 * ( (mark_env - noise_floor) * (mark_env - noise_floor) -
1590 					 (space_env - noise_floor) * (space_env - noise_floor));
1591 
1592 			// Using the logarithm of the logic_level tells the
1593 			// bit synchronization and character decoding which
1594 			// samples were decoded well, and which poorly.
1595 			// This helps fish signals out of the noise.
1596 			int mark_state = log(1 + abs(logic_level));
1597 			if (logic_level < 0)
1598 				mark_state = -mark_state;
1599 			m_early_accumulator += mark_state;
1600 			m_prompt_accumulator += mark_state;
1601 			m_late_accumulator += mark_state;
1602 
1603 			// An average of the magnitude of the accumulator
1604 			// is taken at the sample point, as well as a quarter
1605 			// bit before and after. This allows the code to see
1606 			// the best time to sample the signal without relying
1607 			// on (noisy) null crossings.
1608 			if (m_sample_count >= m_next_early_event) {
1609 				m_average_early_signal = decayavg(
1610 						m_average_early_signal,
1611 						fabs(m_early_accumulator), 64);
1612 				m_next_early_event += m_bit_sample_count;
1613 				m_early_accumulator = 0;
1614 			}
1615 
1616 			if (m_sample_count >= m_next_late_event) {
1617 				m_average_late_signal = decayavg(
1618 						m_average_late_signal,
1619 						fabs(m_late_accumulator), 64);
1620 				m_next_late_event += m_bit_sample_count;
1621 				m_late_accumulator = 0;
1622 			}
1623 
1624 			// the end of a signal pulse
1625 			// the accumulator should be at maximum deviation
1626 			m_pulse_edge_event = m_sample_count >= m_next_prompt_event;
1627 			if (m_pulse_edge_event) {
1628 				m_average_prompt_signal = decayavg(
1629 						m_average_prompt_signal,
1630 						fabs(m_prompt_accumulator), 64);
1631 				m_next_prompt_event += m_bit_sample_count;
1632 				m_averaged_mark_state = m_prompt_accumulator;
1633 				if (m_ptr_navtex->get_reverse())
1634 					m_averaged_mark_state = -m_averaged_mark_state;
1635 				m_prompt_accumulator = 0;
1636 			}
1637 
1638 			switch (m_state) {
1639 				case SYNC_SETUP:
1640 					m_error_count = 0;
1641 					m_shift = false;
1642 					set_state(SYNC);
1643 					break;
1644 				case SYNC:
1645 				case READ_DATA:
1646 					if (m_pulse_edge_event)
1647 						handle_bit_value(m_averaged_mark_state);
1648 			}
1649 
1650 			m_sample_count++;
1651 		}
1652 		compute_metric();
1653 	}
1654 
1655 	/// This updates the window label according to the state.
set_label_from_state(void) const1656 	void set_label_from_state(void) const
1657 	{
1658 		put_status( state_to_str(m_state) );
1659 	}
1660 
1661 private:
1662 	/// Each received message is pushed in this queue, so it can be read by XML/RPC.
1663 	syncobj m_sync_rx ;
1664 	std::queue< std::string > m_received_messages ;
1665 
display_message(ccir_message & ccir_msg,const std::string & alt_string)1666 	void display_message( ccir_message & ccir_msg, const std::string & alt_string ) {
1667 		if( ccir_msg.size() >= (size_t)progdefaults.NVTX_MinSizLoggedMsg )
1668 		{
1669 			try
1670 			{
1671 				ccir_msg.display(alt_string);
1672 				put_received_message( alt_string );
1673 			} catch( const std::exception & exc ) {
1674 				LOG_WARN("Caught %s", exc.what() );
1675 			}
1676 		}
1677 		else
1678 		{
1679 			LOG_INFO("Do not log short message:%s", ccir_msg.c_str() );
1680 		}
1681 	}
1682 
1683 	/// Called by the engine each time a message is saved.
put_received_message(const std::string & message)1684 	void put_received_message( const std::string &message )
1685 	{
1686 		guard_lock g( m_sync_rx.mtxp() );
1687 		LOG_INFO("%s", message.c_str() );
1688 		m_received_messages.push( message );
1689 		m_sync_rx.signal();
1690 	}
1691 
1692 public:
1693 	/// Returns a received message, by chronological order.
get_received_message(double max_seconds)1694 	std::string get_received_message( double max_seconds )
1695 	{
1696 		guard_lock g( m_sync_rx.mtxp() );
1697 
1698 		LOG_DEBUG("Delay=%f", max_seconds );
1699 		if( m_received_messages.empty() )
1700 		{
1701 			if( ! m_sync_rx.wait(max_seconds) ) return "Timeout";
1702 		}
1703 		std::string message = m_received_messages.front();
1704 		m_received_messages.pop();
1705 		return message ;
1706 	}
1707 
1708 	// http://www.arachnoid.com/JNX/index.html
1709 	// "NAUTICAL" becomes:
1710 	// rep alpha rep alpha N alpha A alpha U N T A I U C T A I L C blank A blank L
create_fec(const std::string & str) const1711 	std::string create_fec( const std::string & str ) const
1712 	{
1713 		std::string res ;
1714 		const size_t sz = str.size();
1715 
1716 		static const size_t offset = 2 ;
1717 		for( size_t i = 0 ; i < offset ; ++i ) {
1718 			res.push_back( code_rep );
1719 			res.push_back( code_alpha );
1720 		}
1721 
1722 		for ( size_t i = 0; i < sz; ++i ) {
1723 			res.push_back( str[i] );
1724 			res.push_back( i >= offset ? str[ i - offset ] : code_alpha );
1725 		}
1726 
1727 		for( size_t i = 0 ; i < offset ; ++i ) {
1728 			res.push_back( code_char32 );
1729 			res.push_back( str[ sz - offset + i ] );
1730 		}
1731 		return res;
1732 	}
1733 
1734 	/// Note path std::string can contain null characters. TODO: Beware of the extra copy constructor.
encode(const std::string & str) const1735 	std::string encode( const std::string & str ) const
1736 	{
1737 		std::string res ;
1738 		bool shift = false ;
1739 		for ( size_t i = 0, sz = str.size(); i < sz; ++ i ) {
1740 			m_ccir476.char_to_code(res, str[i], shift );
1741 		}
1742 		return res;
1743 	}
1744 
tx_flush()1745 	void tx_flush()
1746 	{
1747 		if( m_tx_counter != 0 ) {
1748 			m_ptr_navtex->ModulateXmtr( m_tx_buf, m_tx_counter );
1749 			m_tx_counter = 0 ;
1750 		}
1751 	}
1752 
1753 	/// Input value must be between -1 and 1
add_sample(double sam)1754 	void add_sample( double sam )
1755 	{
1756 		m_tx_buf[ m_tx_counter++ ] = sam ;
1757 
1758 		if( m_tx_counter == m_tx_block_len ) {
1759 			tx_flush();
1760 		}
1761 	}
1762 
1763 // REMI : Note change to send_sine
send_sine(double seconds,double freq)1764 	void send_sine( double seconds, double freq )
1765 	{
1766 		static double phase = 0;
1767 		int nb_samples = seconds * m_ptr_navtex->get_samplerate();
1768 		double max_level = 0.9;//0.99 ; // Between -1.0 and 1.0
1769 		double ratio = 2.0 * M_PI * (double)freq / (double)m_ptr_navtex->get_samplerate() ;
1770 		for (int i = 0; i < nb_samples ; ++i )
1771 		{
1772 			add_sample( max_level * sin( phase += ratio));//i * ratio ) );
1773 			if (phase > 2.0 * M_PI) phase -= 2.0*M_PI;
1774 		}
1775 	}
1776 
send_phasing(int seconds)1777 	void send_phasing( int seconds )
1778 	{
1779 		send_sine( seconds, m_center_frequency_f );
1780 	}
1781 
send_bit(bool bit)1782 	void send_bit( bool bit )
1783 	{
1784 		send_sine( 1.0 / (double)m_baud_rate, bit ? m_mark_f : m_space_f );
1785 	}
1786 
send_string(const std::string & msg)1787 	void send_string( const std::string & msg )
1788 	{
1789 		std::string encod = encode( msg );
1790 		std::string sevenbits = create_fec( encod );
1791 
1792 		for( size_t i = 0, sz = sevenbits.size(); i < sz; i++ )
1793 		{
1794 			char tmp_stat[64];
1795 			snprintf( tmp_stat, sizeof(tmp_stat), "Transmission %d%%", (int)( 100.0  * ( i + 1.0 ) / sz ) );
1796 			put_status( tmp_stat );
1797 
1798 			char c = sevenbits[i];
1799 			for( size_t j = 0; j < 7 ; ++j, c >>= 1 )
1800 			{
1801 				send_bit( c & 1 );
1802 			}
1803 		}
1804 	}
send_message(const std::string & msg,bool is_first,bool is_last)1805 	void send_message( const std::string & msg, bool is_first, bool is_last )
1806 	{
1807 		put_status( "Transmission" );
1808 		m_tx_counter = 0 ;
1809 
1810 		if( m_only_sitor_b )
1811 		{
1812 			send_string( msg );
1813 		}
1814 		else
1815 		{
1816 			put_status( "Phasing" );
1817 			send_phasing( is_first ? 10.0 : 5.0 );
1818 			char preamble[64];
1819 			const char origin = 'Z' ; // Never seen this value.
1820 			const char subject = 'I' ; // This code is not used.
1821 			snprintf( preamble, sizeof(preamble), "ZCZC %c%c%02d\r\n", origin, subject, m_message_counter );
1822 			m_message_counter = ( m_message_counter + 1 ) % 100 ;
1823 
1824 			/// The extra cr-nl before NNNN is not in the specification but clarify things.
1825 			std::string full_msg = preamble + msg + "\r\nNNNN\r\n\n";
1826 			send_string( full_msg );
1827 
1828 			// 5 or more seconds of phasing signal and another message starting with "ZCZC" or
1829 			// an end of emission idle signal alpha for at least 2 seconds.  */
1830 			if( is_last ) {
1831 				put_status( "Trailer" );
1832 				send_phasing(2.0);
1833 			}
1834 		}
1835 		tx_flush();
1836 		put_status( "" );
1837 	}
1838 
append_message_to_send(const std::string & msg)1839 	void append_message_to_send( const std::string & msg )
1840 	{
1841 		guard_lock g( &m_mutex_tx );
1842 		m_tx_msg_queue.push_back( msg );
1843 	}
1844 
transmit_message_async(const std::string & msg)1845 	void transmit_message_async( const std::string & msg )
1846 	{
1847 		LOG_INFO("%s", msg.c_str() );
1848 
1849 		append_message_to_send( msg );
1850 
1851 		bool is_first = true ;
1852 		for(;;)
1853 		{
1854 			guard_lock g( &m_mutex_tx );
1855 
1856 			TxMsgQueueT::iterator it = m_tx_msg_queue.begin(), en = m_tx_msg_queue.end();
1857 			if( it == en ) break ;
1858 			TxMsgQueueT::iterator it_next = it ;
1859 			++it_next ;
1860 			bool is_last = it_next == en ;
1861 			send_message( *it, is_first, is_last );
1862 			is_first = false ;
1863 			m_tx_msg_queue.erase(it);
1864 		}
1865 	}
1866 
process_tx()1867 	void process_tx()
1868 	{
1869 		std::string msg ;
1870 
1871 		for(;;)
1872 		{
1873 			int c = get_tx_char();
1874 			if( c == GET_TX_CHAR_NODATA ) {
1875 				break ;
1876 			}
1877 			msg.push_back( c );
1878 		}
1879 
1880 		for( size_t i = 0 ; i < msg.size(); ++ i)
1881 		{
1882 			put_echo_char( msg[i] );
1883 		}
1884 
1885 		transmit_message_async(msg);
1886 	}
1887 
set_carrier(double freq)1888 	void set_carrier( double freq )
1889 	{
1890 		m_center_frequency_f = freq;
1891 		set_filter_values();
1892 	}
1893 
1894 }; // navtex_implementation
1895 
1896 #ifdef NAVTEX_COMMAND_LINE
1897 /// For testing purpose, this file can be compiled and run separately as a command-line program.
main(int n,const char ** v)1898 int main(int n, const char ** v )
1899 {
1900 	printf("%s\n", v[1] );
1901 	FILE * f = fl_fopen( v[1], "r" );
1902 	fseek( f, 0, SEEK_END );
1903 	long l = ftell( f );
1904 	printf("l=%ld\n", l);
1905 	char * buf = new char[l];
1906 	fseek( f, 0, SEEK_SET );
1907 	size_t lr = fread( buf, 1, l, f );
1908 	if( lr - l ) {
1909 		printf("Err reading\n");
1910 		exit(EXIT_FAILURE);
1911 	};
1912 
1913 	navtex_implementation nv(11025) ;
1914 	double * tmp = new double[l/2];
1915 	const short * shrt = (const short *)buf;
1916 	for( int i = 0; i < l/2; i++ )
1917 		tmp[i] = ( (double)shrt[i] ) / 32767.0;
1918 	nv.process_data( tmp, l / 2 );
1919 	return 0 ;
1920 }
1921 #endif // NAVTEX_COMMAND_LINE
1922 
navtex(trx_mode md)1923 navtex::navtex (trx_mode md)
1924 {
1925 	modem::cap |= CAP_AFC | CAP_REV;
1926 	navtex::mode = md;
1927 	modem::samplerate = 11025;
1928 	modem::bandwidth = 2 * deviation_f ;
1929 	modem::reverse = false ;
1930 	bool only_sitor_b = false ;
1931 	switch( md )
1932 	{
1933 		case MODE_NAVTEX : only_sitor_b = false ;
1934 				   break;
1935 		case MODE_SITORB : only_sitor_b = true ;
1936 				   break;
1937 		default          : LOG_ERROR("Unknown mode");
1938 	}
1939 	m_impl = new navtex_implementation( modem::samplerate, only_sitor_b, this );
1940 }
1941 
~navtex()1942 navtex::~navtex()
1943 {
1944 	if( m_impl )
1945 	{
1946 		delete m_impl ;
1947 	}
1948 }
rx_init()1949 void navtex::rx_init()
1950 {
1951 	put_MODEstatus(modem::mode);
1952 }
1953 
restart()1954 void navtex::restart()
1955 {
1956 }
1957 
rx_process(const double * buf,int len)1958 int  navtex::rx_process(const double *buf, int len)
1959 {
1960 	m_impl->process_data( buf, len );
1961 	return 0;
1962 }
1963 
tx_init()1964 void navtex::tx_init()
1965 {
1966 	videoText(); // In trx/modem.cxx
1967 }
1968 
tx_process()1969 int  navtex::tx_process()
1970 {
1971 	modem::tx_process();
1972 
1973 	m_impl->process_tx();
1974 
1975 	return -1;
1976 }
1977 
set_freq(double freq)1978 void navtex::set_freq( double freq )
1979 {
1980 	modem::set_freq( freq );
1981 	m_impl->set_carrier( freq );
1982 }
1983 
1984 /// This returns the next received message.
get_message(int max_seconds)1985 std::string navtex::get_message(int max_seconds)
1986 {
1987 	return m_impl->get_received_message(max_seconds);
1988 }
1989 
send_message(const std::string & msg)1990 std::string navtex::send_message(const std::string &msg)
1991 {
1992 	m_impl->append_message_to_send(msg);
1993 	start_tx(); // If this is not done.
1994 	return "";
1995 }
1996 
1997