1 // ----------------------------------------------------------------------------
2 // fsq.cxx  --  fsq modem
3 //
4 // Copyright (C) 2015
5 //		Dave Freese, W1HKJ
6 //
7 // This file is part of fldigi.
8 //
9 // Fldigi is free software: you can redistribute it and/or modify
10 // it under the terms of the GNU General Public License as published by
11 // the Free Software Foundation, either version 3 of the License, or
12 // (at your option) any later version.
13 //
14 // Fldigi is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 // GNU General Public License for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with fldigi.  If not, see <http://www.gnu.org/licenses/>.
21 // ----------------------------------------------------------------------------
22 
23 #include <config.h>
24 
25 #include <stdlib.h>
26 #include <iostream>
27 #include <fstream>
28 #include <string>
29 #include <sstream>
30 #include <iomanip>
31 #include <cmath>
32 #include <vector>
33 #include <libgen.h>
34 
35 #include <FL/filename.H>
36 #include "progress.h"
37 #include "fsq.h"
38 #include "complex.h"
39 #include "fl_digi.h"
40 #include "misc.h"
41 #include "fileselect.h"
42 #include "threads.h"
43 #include "debug.h"
44 #include "re.h"
45 
46 #include "configuration.h"
47 #include "qrunner.h"
48 #include "fl_digi.h"
49 #include "status.h"
50 #include "main.h"
51 #include "icons.h"
52 #include "ascii.h"
53 #include "timeops.h"
54 #include "flmisc.h"
55 
56 #include "test_signal.h"
57 
58 using namespace std;
59 
60 #include "fsq_varicode.cxx"
61 
62 void  clear_xmt_arrays();
63 
64 #define SQLFILT_SIZE 64
65 
66 #define NIT std::string::npos
67 #define txcenterfreq  1500.0
68 
69 int fsq::symlen = 4096; // nominal symbol length; 3 baud
70 
71 static const char *FSQBOL = " \n";
72 static const char *FSQEOL = "\n ";
73 static const char *FSQEOT = "  \b  ";
74 static const char *fsq_lf = "\n";
75 static const char *fsq_bot = "<bot>";
76 static const char *fsq_eol = "<eol>";
77 static const char *fsq_eot = "<eot>";
78 
79 static std::string fsq_string;
80 static std::string fsq_delayed_string;
81 
82 static bool enable_audit_log = false;
83 static bool enable_heard_log = false;
84 
85 #include "fsq-pic.cxx"
86 
87 static notify_dialog *alert_window = 0;
88 
post_alert(std::string s1,double timeout=0.0,std::string s2="")89 void post_alert(std::string s1, double timeout = 0.0, std::string s2 = "")
90 {
91 	if (active_modem && (active_modem->get_mode() == MODE_FSQ)  && !s2.empty()) {
92 		active_modem->send_ack(s2);
93 	}
94 
95 	if (!alert_window) alert_window = new notify_dialog;
96 	alert_window->notify(s1.c_str(), timeout);
97 	REQ(show_notifier, alert_window);
98 
99 	display_fsq_mon_text(s1, FTextBase::ALTR);
100 
101 }
102 
103 // nibbles table used for fast conversion from tone difference to symbol
104 
sz2utf8(std::string s)105 static std::string sz2utf8(std::string s)
106 {
107 	char dest[4*s.length() + 1]; // if every char were utf8
108 	int numbytes = fl_utf8froma(dest, sizeof(dest) - 1, s.c_str(), s.length());
109 	if (numbytes > 0) return dest;
110 	return s;
111 }
112 
113 //static int nibbles[199];
init_nibbles()114 void fsq::init_nibbles()
115 {
116 	int nibble = 0;
117 	for (int i = 0; i < 199; i++) {
118 		nibble = floor(0.5 + (i - 99)/3.0);
119 		// allow for wrap-around (33 tones for 32 tone differences)
120 		if (nibble < 0) nibble += 33;
121 		if (nibble > 32) nibble -= 33;
122 		// adjust for +1 symbol at the transmitter
123 		nibble--;
124 		nibbles[i] = nibble;
125 	}
126 }
127 
128 // note:
129 // display_fsq_rx_text and
130 // display_fsq_mon_text
131 // use a REQ(...) access to the UI
132 // it is not necessary to indirectly call either write_rx_mon_char or
133 // write_mon_tx_char using the REQ access mechanism
134 
write_rx_mon_char(int ch)135 void write_rx_mon_char(int ch)
136 {
137 	int ach = ch & 0xFF;
138 	if (!progdefaults.fsq_directed) {
139 		display_fsq_rx_text(fsq_ascii[ach], FTextBase::FSQ_UND) ;
140 		if (ach == '\n')
141 			display_fsq_rx_text(fsq_lf, FTextBase::FSQ_UND);
142 	}
143 	display_fsq_mon_text(fsq_ascii[ach], FTextBase::RECV);
144 	if (ach == '\n')
145 		display_fsq_mon_text(fsq_lf, FTextBase::RECV);
146 }
147 
write_mon_tx_char(int ch)148 void write_mon_tx_char(int ch)
149 {
150 	int ach = ch & 0xFF;
151 
152 	display_fsq_mon_text(fsq_ascii[ach], FTextBase::FSQ_TX);
153 	if (ach == '\n')
154 		display_fsq_mon_text(fsq_lf, FTextBase::FSQ_TX);
155 }
156 
printit(double speed,int bandwidth,int symlen,int bksize,int peak_hits,int tone)157 void printit(double speed, int bandwidth, int symlen, int bksize, int peak_hits, int tone)
158 {
159 	std::ostringstream it;
160 	it << "\nSpeed.......... " << speed     << "\nBandwidth...... " << bandwidth;
161 	it << "\nSymbol length.. " << symlen    << "\nBlock size..... " << bksize;
162 	it << "\nMinimum Hits... " << peak_hits << "\nBasetone....... " << tone << "\n";
163 	display_fsq_mon_text(it.str(), FTextBase::ALTR);
164 }
165 
fsq(trx_mode md)166 fsq::fsq(trx_mode md) : modem()
167 {
168 	modem::set_freq(1500); // default Rx/Tx center frequency
169 
170 	mode = md;
171 	samplerate = SR;
172 	fft = new g_fft<double>(FFTSIZE);
173 	snfilt = new Cmovavg(SQLFILT_SIZE);
174 
175 	noisefilt = new Cmovavg(32);
176 	sigfilt = new Cmovavg(8);
177 
178 //	baudfilt = new Cmovavg(3);
179 	movavg_size = progdefaults.fsq_movavg;
180 	if (movavg_size < 1) movavg_size = progdefaults.fsq_movavg = 1;
181 	if (movavg_size > MOVAVGLIMIT) movavg_size = progdefaults.fsq_movavg = MOVAVGLIMIT;
182 	for (int i = 0; i < NUMBINS; i++) binfilt[i] = new Cmovavg(movavg_size);
183 	spacing = 3;
184 	txphase = 0;
185 	basetone = 333;
186 
187 	picfilter = new C_FIR_filter();
188 	picfilter->init_lowpass(257, 1, 500.0 / samplerate);
189 	phase = 0;
190 	phidiff = 2.0 * M_PI * frequency / samplerate;
191 	prevz = cmplx(0,0);
192 
193 	bkptr = 0;
194 	peak_counter = 0;
195 	peak = last_peak = 0;
196 	max = 0;
197 	curr_nibble = prev_nibble = 0;
198 	s2n = 0;
199 	ch_sqlch_open = false;
200 	memset(rx_stream, 0, sizeof(rx_stream));
201 	rx_text.clear();
202 
203 	for (int i = 0; i < BLOCK_SIZE; i++)
204 		a_blackman[i] = blackman(1.0 * i / BLOCK_SIZE);
205 
206 	fsq_tx_image = false;
207 
208 	init_nibbles();
209 
210 	start_aging();
211 
212 	show_mode();
213 
214 	restart();
215 	toggle_logs();
216 }
217 
~fsq()218 fsq::~fsq()
219 {
220 	delete fft;
221 	delete snfilt;
222 
223 	delete sigfilt;
224 	delete noisefilt;
225 
226 	for (int i = 0; i < NUMBINS; i++)
227 		delete binfilt[i];
228 //	delete baudfilt;
229 	delete picfilter;
230 	REQ(close_fsqMonitor);
231 	stop_sounder();
232 	stop_aging();
233 
234 	if (enable_audit_log)
235 		audit_log.close();
236 	if (enable_heard_log)
237 		heard_log.close();
238 };
239 
tx_init()240 void  fsq::tx_init()
241 {
242 	tone = prevtone = 0;
243 	txphase = 0;
244 	send_bot = true;
245 	mycall = progdefaults.myCall;
246 	if (progdefaults.fsq_lowercase)
247 		for (size_t n = 0; n < mycall.length(); n++) mycall[n] = tolower(mycall[n]);
248 	videoText();
249 }
250 
rx_init()251 void  fsq::rx_init()
252 {
253 	set_freq(frequency);
254 	bandwidth = 33 * spacing * samplerate / FSQ_SYMLEN;
255 	bkptr = 0;
256 	peak_counter = 0;
257 	peak = last_peak = 0;
258 	max = 0;
259 	curr_nibble = prev_nibble = 0;
260 	s2n = 0;
261 	ch_sqlch_open = false;
262 	memset(rx_stream, 0, sizeof(rx_stream));
263 
264 	rx_text.clear();
265 	for (int i = 0; i < NUMBINS; i++) {
266 		tones[i] = 0.0;
267 		binfilt[i]->reset();
268 	}
269 
270 	pixel = 0;
271 	amplitude = 0;
272 	phase = 0;
273 	prevz = cmplx(0,0);
274 	image_counter = 0;
275 	RXspp = 10; // 10 samples per pixel
276 	state = TEXT;
277 }
278 
init()279 void fsq::init()
280 {
281 	modem::init();
282 
283 	sounder_interval = progdefaults.fsq_sounder;
284 	start_sounder(sounder_interval);
285 
286 	rx_init();
287 }
288 
set_freq(double f)289 void fsq::set_freq(double f)
290 {
291 	frequency = f;
292 	modem::set_freq(frequency);
293 	basetone = ceil(1.0*(frequency - bandwidth / 2) * FSQ_SYMLEN / samplerate);
294 	tx_basetone = ceil((get_txfreq() - bandwidth / 2) * FSQ_SYMLEN / samplerate );
295 	int incr = basetone % spacing;
296 	basetone -= incr;
297 	tx_basetone -= incr;
298 }
299 
show_mode()300 void fsq::show_mode()
301 {
302         if (speed == 1.5 )
303       		put_MODEstatus("FSQ-1.5");
304 	else if (speed == 2.0)
305 		put_MODEstatus("FSQ-2");
306 	else if (speed == 3.0)
307 		put_MODEstatus("FSQ-3");
308 	else if (speed == 4.5)
309 		put_MODEstatus("FSQ-4.5");
310 	else
311 		put_MODEstatus("FSQ-6");
312 }
313 
adjust_for_speed()314 void fsq::adjust_for_speed()
315 {
316 	speed = progdefaults.fsqbaud;
317 
318 	if( speed == 1.5 ) {
319 	        symlen = 8192;
320 	} else if (speed == 2.0) {
321 		symlen = 6144;
322 	} else if (speed == 3.0) {
323 		symlen = 4096;
324 	} else if (speed == 4.5) {
325 		symlen = 3072;
326 	} else { // speed == 6
327 		symlen = 2048;
328 	}
329 	show_mode();
330 }
331 
toggle_logs()332 void fsq::toggle_logs()
333 {
334 	if (enable_heard_log != progdefaults.fsq_enable_heard_log) {
335 		enable_heard_log = progdefaults.fsq_enable_heard_log;
336 		if (heard_log.is_open()) heard_log.close();
337 	}
338 
339 	if (enable_audit_log != progdefaults.fsq_enable_audit_log) {
340 		enable_audit_log = progdefaults.fsq_enable_audit_log;
341 		if (audit_log.is_open()) audit_log.close();
342 	}
343 
344 	if (enable_heard_log) {
345 		heard_log_fname = progdefaults.fsq_heard_log;
346 		std::string sheard = TempDir;
347 		sheard.append(heard_log_fname);
348 		heard_log.open(sheard.c_str(), ios::app);
349 
350 		heard_log << "==================================================\n";
351 		heard_log << "Heard log: " << zdate() << ", " << ztime() << "\n";
352 		heard_log << "==================================================\n";
353 	}
354 
355 	if (enable_audit_log) {
356 		audit_log_fname = progdefaults.fsq_audit_log;
357 		std::string saudit = TempDir;
358 		saudit.append(audit_log_fname);
359 		audit_log.close();
360 		audit_log.open(saudit.c_str(), ios::app);
361 
362 		audit_log << "==================================================\n";
363 		audit_log << "Audit log: " << zdate() << ", " << ztime() << "\n";
364 		audit_log << "==================================================\n";
365 	}
366 }
367 
restart()368 void fsq::restart()
369 {
370 	set_freq(frequency);
371 
372 	peak_hits = progdefaults.fsqhits;
373 	adjust_for_speed();
374 
375 	mycall = progdefaults.myCall;
376 	if (progdefaults.fsq_lowercase)
377 		for (size_t n = 0; n < mycall.length(); n++) mycall[n] = tolower(mycall[n]);
378 
379 	movavg_size = progdefaults.fsq_movavg;
380 	if (movavg_size < 1) movavg_size = progdefaults.fsq_movavg = 1;
381 	if (movavg_size > MOVAVGLIMIT) movavg_size = progdefaults.fsq_movavg = MOVAVGLIMIT;
382 
383 	for (int i = 0; i < NUMBINS; i++) binfilt[i]->setLength(movavg_size);
384 
385 	printit(speed, bandwidth, symlen, SHIFT_SIZE, peak_hits, basetone);
386 
387 }
388 
valid_char(int ch)389 bool fsq::valid_char(int ch)
390 {
391 	if ( ch ==  10 || ch == 163 || ch == 176 ||
392 		ch == 177 || ch == 215 || ch == 247 ||
393 		(ch > 31 && ch < 128))
394 		return true;
395 	return false;
396 }
397 
398 //=====================================================================
399 // receive processing
400 //=====================================================================
401 
fsq_squelch_open()402 bool fsq::fsq_squelch_open()
403 {
404 	return ch_sqlch_open || metric >= progStatus.sldrSquelchValue;
405 }
406 
407 static string triggers = " !#$%&'()*+,-.;<=>?@[\\]^_{|}~";
408 static string allcall = "allcall";
409 static string cqcqcq = "cqcqcq";
410 
411 static fre_t call("([[:alnum:]]?[[:alpha:]/]+[[:digit:]]+[[:alnum:]/]+)", REG_EXTENDED);
412 
413 // test for valid callsign
414 // returns:
415 // 0 - not a callsign
416 // 1 - mycall
417 // 2 - allcall
418 // 4 - cqcqcq
419 // 8 - any other valid call
420 
valid_callsign(std::string s)421 int fsq::valid_callsign(std::string s)
422 {
423 	if (s.length() < 3) return 0;
424 	if (s.length() > 20) return 0;
425 
426 	if (s == allcall) return 2;
427 	if (s == cqcqcq) return 4;
428 	if (s == mycall) return 1;
429 	if (s.find("Heard") != string::npos) return 0;
430 
431 	static char sz[21];
432 	memset(sz, 0, 21);
433 	strcpy(sz, s.c_str());
434 	bool matches = call.match(sz);
435 	return (matches ? 8 : 0);
436 }
437 
parse_rx_text()438 void fsq::parse_rx_text()
439 {
440 	char ztbuf[20];
441 	struct timeval tv;
442 	gettimeofday(&tv, NULL);
443 	struct tm tm;
444 	time_t t_temp;
445 	t_temp=(time_t)tv.tv_sec;
446 	gmtime_r(&t_temp, &tm);
447 	strftime(ztbuf, sizeof(ztbuf), "%Y%m%d,%H%M%S", &tm);
448 
449 	toprint.clear();
450 
451 	if (rx_text.empty()) return;
452 	if (rx_text.length() > 65536) {
453 		rx_text.clear();
454 		return;
455 	}
456 
457 	state = TEXT;
458 	size_t p = rx_text.find(':');
459 	if (p == 0) {
460 		rx_text.erase(0,1);
461 		return;
462 	}
463 	if (p == std::string::npos ||
464 		rx_text.length() < p + 2) {
465 		return;
466 	}
467 
468 	std::string rxcrc = rx_text.substr(p+1,2);
469 
470 	station_calling.clear();
471 
472 	int max = p+1;
473 	if (max > 20) max = 20;
474 	std::string substr;
475 	for (int i = 1; i < max; i++) {
476 		if (rx_text[p-i] <= ' ' || rx_text[p-i] > 'z') {
477 			rx_text.erase(0, p+1);
478 			return;
479 		}
480 		substr = rx_text.substr(p-i, i);
481 		if ((crc.sval(substr) == rxcrc) && valid_callsign(substr)) {
482 			station_calling = substr;
483 			break;
484 		}
485 	}
486 
487 	if (station_calling == mycall) { // do not display any of own rx stream
488 		LOG_ERROR("Station calling is mycall: %s", station_calling.c_str());
489 		rx_text.erase(0, p+3);
490 		return;
491 	}
492 	if (!station_calling.empty()) {
493 		REQ(add_to_heard_list, station_calling, szestimate);
494 		if (enable_heard_log) {
495 			std::string sheard = ztbuf;
496 			sheard.append(",").append(station_calling);
497 			sheard.append(",").append(szestimate).append("\n");
498 			heard_log << sheard;
499 			heard_log.flush();
500 		}
501 	}
502 
503 // remove station_calling, colon and checksum
504 	rx_text.erase(0, p+3);
505 
506 // extract all directed callsigns
507 // look for 'allcall', 'cqcqcq' or mycall
508 
509 	bool all = false;
510 	bool directed = false;
511 
512 // test next word in string
513 	size_t tr_pos = 0;
514 	char tr = rx_text[tr_pos];
515 	size_t trigger = triggers.find(tr);
516 
517 // strip any leading spaces before either text or first directed callsign
518 
519 	while (rx_text.length() > 1 &&
520 		triggers.find(rx_text[0]) != std::string::npos)
521 		rx_text.erase(0,1);
522 
523 // find first word
524 	while ( tr_pos < rx_text.length()
525 			&& ((trigger = triggers.find(rx_text[tr_pos])) == std::string::npos) ) {
526 		tr_pos++;
527 	}
528 
529 	while (trigger != std::string::npos && tr_pos < rx_text.length()) {
530 		int word_is = valid_callsign(rx_text.substr(0, tr_pos));
531 
532 		if (word_is == 0) {
533 			rx_text.insert(0," ");
534 			break; // not a callsign
535 		}
536 		if (word_is == 1) {
537 			directed = true; // mycall
538 		}
539 		// test for cqcqcq and allcall
540 		else if (word_is != 8)
541 			all = true;
542 
543 		rx_text.erase(0, tr_pos);
544 
545 		while (rx_text.length() > 1 &&
546 			(rx_text[0] == ' ' && rx_text[1] == ' '))
547 			rx_text.erase(0,1);
548 
549 		if (rx_text[0] != ' ') break;
550 		rx_text.erase(0, 1);
551 
552 		tr_pos = 0;
553 		tr = rx_text[tr_pos];
554 		trigger = triggers.find(tr);
555 		while ( tr_pos < rx_text.length() && (trigger == std::string::npos) ) {
556 			tr_pos++;
557 			tr = rx_text[tr_pos];
558 			trigger = triggers.find(tr);
559 		}
560 	}
561 
562 	if ( (all == false) && (directed == false)) {
563 		rx_text.clear();
564 		return;
565 	}
566 
567 // remove eot if present
568 	if (rx_text.length() > 3) rx_text.erase(rx_text.length() - 3);
569 
570 	toprint.assign(station_calling).append(":");
571 
572 // test for trigger
573 	tr = rx_text[0];
574 	trigger = triggers.find(tr);
575 
576 	if (trigger == NIT) {
577 		tr = ' '; // force to be text line
578 		rx_text.insert(0, " ");
579 	}
580 
581 // if asleep suppress all but the * trigger
582 
583 	if (btn_SELCAL->value() == 0) {
584 		if (tr == '*') parse_star();
585 		rx_text.clear();
586 		return;
587 	}
588 
589 // now process own call triggers
590 	if (directed) {
591 		switch (tr) {
592 			case ' ': parse_space(false);   break;
593 			case '?': parse_qmark();   break;
594 			case '*': parse_star();    break;
595 			case '+': parse_plus();    break;
596 			case '-': break;//parse_minus();   break;
597 			case ';': parse_relay();    break;
598 			case '!': parse_repeat();    break;
599 			case '~': parse_delayed_repeat();   break;
600 			case '#': parse_pound();   break;
601 			case '$': parse_dollar();  break;
602 			case '@': parse_at();      break;
603 			case '&': parse_amp();     break;
604 			case '^': parse_carat();   break;
605 			case '%': parse_pcnt();    break;
606 			case '|': parse_vline();   break;
607 			case '>': parse_greater(); break;
608 			case '<': parse_less();    break;
609 			case '[': parse_relayed(); break;
610 		}
611 	}
612 
613 // if allcall; only respond to the ' ', '*', '#', '%', and '[' triggers
614 	else {
615 		switch (tr) {
616 			case ' ': parse_space(true);   break;
617 			case '*': parse_star();    break;
618 			case '#': parse_pound();   break;
619 			case '%': parse_pcnt();    break;
620 			case '[': parse_relayed(); break;
621 		}
622 	}
623 
624 	rx_text.clear();
625 }
626 
parse_space(bool all)627 void fsq::parse_space(bool all)
628 {
629 	display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
630 }
631 
parse_qmark(std::string relay)632 void fsq::parse_qmark(std::string relay)
633 {
634 	std::string response = station_calling;
635 	if (!relay.empty()) response.append(";").append(relay);
636 	response.append(" snr=").append(szestimate);
637 	reply(response);
638 	display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
639 }
640 
parse_dollar(std::string relay)641 void fsq::parse_dollar(std::string relay)
642 {
643 	std::string response = station_calling;
644 	if (!relay.empty()) response.append(";").append(relay);
645 	response.append(" Heard:\n");
646 	response.append(heard_list());
647 	reply(response);
648 	display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
649 }
650 
parse_star()651 void fsq::parse_star()
652 {
653 	REQ(enableSELCAL);
654 	reply(std::string(station_calling).append(" ack"));
655 }
656 
657 // immediate repeat of msg
parse_repeat()658 void fsq::parse_repeat()
659 {
660 	std::string response;
661 	rx_text.erase(0, 1);
662 	if (rx_text[0] != ' ') rx_text.insert(0, " ");
663 	response.assign(" ");
664 	response.append(rx_text);
665 	reply(response);
666 	display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
667 }
668 
669 // delayed repeat of msg
parse_delayed_repeat()670 void fsq::parse_delayed_repeat()
671 {
672 	std::string response;
673 	rx_text.erase(0, 1);
674 	if (rx_text[0] != ' ') rx_text.insert(0, " ");
675 	response.assign(" ");
676 	response.append(rx_text);
677 	delayed_reply(response, 15);
678 	display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
679 }
680 
681 // extended relay of msg
682 // k2a sees : k1a:; k3a hi
683 // k2a sends: k2a:22k3a[k1a] hi
parse_relay()684 void fsq::parse_relay()
685 {
686 	std::string send_txt = rx_text;
687 	send_txt.erase(0,1); // remove ';'
688 	if (send_txt.empty()) return;
689 	while (send_txt[0] == ' ' && !send_txt.empty())
690 		send_txt.erase(0,1); // remove leading spaces
691 	// find trigger
692 	size_t p = 0;
693 	while ((triggers.find(send_txt[p]) == NIT) && p < send_txt.length()) p++;
694 	std::string response = string("[").append(station_calling).append("]");
695 	send_txt.insert(p, response);
696 	if ((p = send_txt.find('^')) != NIT) send_txt.insert(p, "^");
697 	display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
698 	reply(send_txt);
699 }
700 
701 // k3a sees : k2a: [k1a]@
702 // or       : k2a:[k1a]@
parse_relayed()703 void fsq::parse_relayed()
704 {
705 	std::string relayed = "";
706 
707 	size_t p1 = rx_text.find('[');
708 	if (p1 == NIT) {
709 		LOG_ERROR("%s", "missing open bracket");
710 		return;
711 	}
712 	rx_text.erase(0,p1 + 1);
713 	if (rx_text.empty()) return;
714 
715 	size_t p2 = rx_text.find(']');
716 	if (p2 == NIT) {
717 		LOG_ERROR("%s", "missing end bracket");
718 		return;
719 	}
720 	relayed = rx_text.substr(0, p2);
721 	rx_text.erase(0, p2 + 1);
722 	if (rx_text.empty()) return;
723 
724 	if (triggers.find(rx_text[0]) == NIT) {
725 		LOG_ERROR("%s", "invalid relay trigger");
726 		return;
727 	}
728 // execute trigger
729 	switch (rx_text[0]) {
730 		case ' ' : {
731 			std::string response = station_calling;
732 			response.append(";").append(relayed).append(rx_text);
733 			display_fsq_rx_text(toprint.append(response).append("\n"), FTextBase::FSQ_DIR);
734 		} break;
735 		case '$' : parse_dollar(relayed); break;
736 		case '&' : parse_amp(relayed); break;
737 		case '?' : parse_qmark(relayed); break;
738 		case '@' : parse_at(relayed); break;
739 		case '^' : parse_carat(relayed); break;
740 		case '|' : parse_vline(relayed); break;
741 		case '#' : parse_pound(relayed); break;
742 		case '<' : parse_less(relayed); break;
743 		case '>' : parse_greater(relayed); break;
744 		default : break;
745 	}
746 }
747 
748 // rx_text[0] will be '#'
749 
parse_pound(std::string relay)750 void fsq::parse_pound(std::string relay)
751 {
752 	size_t p1 = NIT, p2 = NIT;
753 	std::string fname = "";
754 	p1 = rx_text.find('[');
755 	if (p1 != NIT) {
756 		p2 = rx_text.find(']', p1);
757 		if (p2 != NIT) {
758 			fname = rx_text.substr(p1 + 1, p2 - p1 - 1);
759 			fname = fl_filename_name(fname.c_str());
760 		} else p2 = 0;
761 	} else p2 = 0;
762 	if (fname.empty()) {
763 		if (!relay.empty()) fname = relay;
764 		else fname = station_calling;
765 		fname.append(".txt");
766 	}
767 	if (fname.find(".txt") == std::string::npos) fname.append(".txt");
768 	if (rx_text[rx_text.length() -1] != '\n') rx_text.append("\n");
769 
770 	size_t p3 = NIT;
771 	while( (p3 = fname.find("/")) != NIT) fname[p3] = '.';
772 	while( (p3 = fname.find("\\")) != NIT) fname[p3] = '.';
773 
774 	std::ofstream rxfile;
775 	fname.insert(0, TempDir);
776 	if (progdefaults.always_append) {
777 		rxfile.open(fname.c_str(), ios::app);
778 	} else {
779 		rxfile.open(fname.c_str(), ios::out);
780 	}
781 	if (!rxfile) return;
782 	if (progdefaults.add_fsq_msg_dt) {
783 		rxfile << "Received: " << zdate() << ", " << ztime() << "\n";
784 		rxfile << rx_text.substr(p2+1) << "\n";
785 	} else
786 		rxfile << rx_text.substr(p2+1);
787 
788 	rxfile.close();
789 
790 	display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
791 
792 	std::string response = station_calling;
793 	if (!relay.empty()) response.append(";").append(relay);
794 	response.append(" ack");
795 	reply(response);
796 }
797 
parse_plus(std::string relay)798 void fsq::parse_plus(std::string relay)
799 {
800 	size_t p1 = NIT, p2 = NIT;
801 	std::string fname = "";
802 	p1 = rx_text.find('[');
803 	if (p1 != NIT) {
804 		p2 = rx_text.find(']', p1);
805 		if (p2 != NIT) {
806 			fname = rx_text.substr(p1 + 1, p2 - p1 - 1);
807 			fname = fl_filename_name(fname.c_str());
808 		} else p2 = 0;
809 	}
810 	if (fname.empty()) {
811 		if (!relay.empty()) fname = relay;
812 		else fname = station_calling;
813 		fname.append(".txt");
814 	}
815 
816 	std::ifstream txfile;
817 	bool append = (fname == station_calling);
818 	std::string pathname = TempDir;
819 	if (append) {
820 		pathname.append(fname).append(".txt");
821 		txfile.open(pathname.c_str());
822 	} else {
823 		pathname.append(fname);
824 		txfile.open(pathname.c_str());
825 	}
826 	if (!txfile) {
827 		reply(std::string(station_calling).append(" not found"));
828 		return;
829 	}
830 	stringstream outtext(station_calling);
831 	outtext << " [" << fname << "]\n";
832 	char ch = txfile.get();
833 	while (!txfile.eof()) {
834 		outtext << ch;
835 		ch = txfile.get();
836 	}
837 	txfile.close();
838 
839 	std::string response = station_calling;
840 	if (!relay.empty()) response.append(";").append(relay);
841 	response.append(outtext.str());
842 	reply(response);
843 }
844 
parse_minus()845 void fsq::parse_minus()
846 {
847 	display_fsq_rx_text(toprint.append(rx_text).append(" nia\n"), FTextBase::FSQ_DIR);
848 	reply(std::string(station_calling).append(" not supported"));
849 }
850 
parse_at(std::string relay)851 void fsq::parse_at(std::string relay)
852 {
853 	std::string response = station_calling;
854 	if (!relay.empty()) response.append(";").append(relay);
855 	response.append(" ").append(progdefaults.myQth);
856 	reply(response);
857 	display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
858 }
859 
parse_amp(std::string relay)860 void fsq::parse_amp(std::string relay)
861 {
862 	std::string response = station_calling;
863 	if (!relay.empty()) response.append(";").append(relay);
864 	response.append(" ").append(progdefaults.fsqQTCtext);
865 	reply(response);
866 	display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
867 }
868 
parse_carat(std::string relay)869 void fsq::parse_carat(std::string relay)
870 {
871 	std::string response = station_calling;
872 	if (!relay.empty()) response.append(";").append(relay);
873 	response.append(" fldigi ").append(PACKAGE_VERSION);
874 	reply(response);
875 	display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
876 }
877 
parse_pcnt()878 void fsq::parse_pcnt()
879 {
880 	switch (rx_text[2]) {
881 		case 'L' :
882 			image_mode = 0; picW = 320; picH = 240;
883 			break;
884 		case 'S' :
885 			image_mode = 1; picW = 160; picH = 120;
886 			break;
887 		case 'F' :
888 			image_mode = 2; picW = 640; picH = 480;
889 			break;
890 		case 'V' :
891 			image_mode = 3; picW = 640; picH = 480;
892 			break;
893 		case 'P' :
894 			image_mode = 4; picW = 240; picH = 300;
895 			break;
896 		case 'p' :
897 			image_mode = 5; picW = 240; picH = 300;
898 			break;
899 		case 'M' :
900 			image_mode = 6; picW = 120; picH = 150;
901 			break;
902 		case 'm' :
903 			image_mode = 7; picW = 120; picH = 150;
904 			break;
905 
906 	}
907 	REQ( fsq_showRxViewer, picW, picH, rx_text[2] );
908 
909 	image_counter = 0;
910 
911 	picf = 0;
912 	row = col = rgb = 0;
913 	state = IMAGE;
914 
915 	display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
916 }
917 
parse_vline(std::string relay)918 void fsq::parse_vline(std::string relay)
919 {
920 	display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
921 
922 	std::string alert = "Message received from ";
923 	if (relay.empty()) alert.append(station_calling);
924 	else alert.append(relay);
925 	alert.append("\n").append("at: ").append(zshowtime()).append("\n");
926 	alert.append(rx_text.substr(1));
927 
928 	REQ(post_alert, alert, progdefaults.fsq_notify_time_out, relay);
929 }
930 
send_ack(std::string relay)931 void fsq::send_ack(std::string relay)
932 {
933 	std::string response = station_calling;
934 	if (!relay.empty()) response.append(";").append(relay);
935 	response.append(" ack");
936 	reply(response);
937 }
938 
939 
parse_greater(std::string relay)940 void fsq::parse_greater(std::string relay)
941 {
942 	std::string response;
943 	response.assign(station_calling);
944 	if (!relay.empty()) response.append(";").append(relay);
945 
946 	double spd = progdefaults.fsqbaud;
947 	if (spd == 1.5 ) {
948 	        spd = 2.0;
949 	        response.append(" 2.0 baud");
950 	} else if (spd == 2.0) {
951 		spd = 3.0;
952 		response.append(" 3.0 baud");
953 	} else if (spd == 3.0) {
954 		spd = 4.5;
955 		response.append(" 4.5 baud");
956 	} else if (spd == 4.5) {
957 		spd = 6.0;
958 		response.append(" 6.0 baud");
959 	} else if (spd == 6.0) {
960 		response.append(" 6.0 baud");
961 	}
962 	progdefaults.fsqbaud = spd;
963 	adjust_for_speed();
964 	reply(response);
965 	display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
966 }
967 
parse_less(std::string relay)968 void fsq::parse_less(std::string relay)
969 {
970 	std::string response;
971 	response.assign(station_calling);
972 	if (!relay.empty()) response.append(";").append(relay);
973 
974 	double spd = progdefaults.fsqbaud;
975 	if (spd == 2.0) {
976 	  	spd = 1.5;
977 		response.append(" 1.5 baud");
978 	} else if (spd == 3.0) {
979 		spd = 2.0;
980 		response.append(" 2.0 baud");
981 	} else if (spd == 4.5) {
982 		spd = 3.0;
983 		response.append(" 3.0 baud");
984 	} else if (spd == 6.0) {
985 		spd = 4.5;
986 		response.append(" 4.5 baud");
987 	}
988 	progdefaults.fsqbaud = spd;
989 	adjust_for_speed();
990 	reply(response);
991 	display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
992 }
993 
lf_check(int ch)994 void fsq::lf_check(int ch)
995 {
996 	static char lfpair[3] = "01";
997 	static char bstrng[4] = "012";
998 
999 	lfpair[0] = lfpair[1];
1000 	lfpair[1] = 0xFF & ch;
1001 
1002 	bstrng[0] = bstrng[1];
1003 	bstrng[1] = bstrng[2];
1004 	bstrng[2] = 0xFF & ch;
1005 
1006 	b_bot = b_eol = b_eot = false;
1007 	if (bstrng[0] == FSQEOT[0]    // find SP SP BS SP
1008 		&& bstrng[1] == FSQEOT[1]
1009 		&& bstrng[2] == FSQEOT[2]
1010 		) {
1011 		b_eot = true;
1012 	} else if (lfpair[0] == FSQBOL[0] && lfpair[1] == FSQBOL[1]) {
1013 		b_bot = true;
1014 	} else if (lfpair[0] == FSQEOL[0] && lfpair[1] == FSQEOL[1]) {
1015 		b_eol = true;
1016 	}
1017 }
1018 
process_symbol(int sym)1019 void fsq::process_symbol(int sym)
1020 {
1021 	int nibble = 0;
1022 	int curr_ch = -1;
1023 
1024 	symbol = sym;
1025 
1026 	nibble = symbol - prev_symbol;
1027 	if (nibble < -99 || nibble > 99) {
1028 		prev_symbol = symbol;
1029 		return;
1030 	}
1031 	nibble = nibbles[nibble + 99];
1032 
1033 // -1 is our idle symbol, indicating we already have our symbol
1034 	if (nibble >= 0) { // process nibble
1035 		curr_nibble = nibble;
1036 
1037 // single-nibble characters
1038 		if ((prev_nibble < 29) & (curr_nibble < 29)) {
1039 			curr_ch = wsq_varidecode[prev_nibble];
1040 
1041 // double-nibble characters
1042 		} else if ( (prev_nibble < 29) &&
1043 					 (curr_nibble > 28) &&
1044 					 (curr_nibble < 32)) {
1045 			curr_ch = wsq_varidecode[prev_nibble * 32 + curr_nibble];
1046 		}
1047 		if (curr_ch > 0) {
1048 			if (enable_audit_log) {
1049 				audit_log << fsq_ascii[curr_ch];// & 0xFF];
1050 				if (curr_ch == '\n') audit_log << '\n';
1051 				audit_log.flush();
1052 			}
1053 			lf_check(curr_ch);
1054 
1055 			if (b_bot) {
1056 				ch_sqlch_open = true;
1057 				rx_text.clear();
1058 			}
1059 
1060 			if (fsq_squelch_open()) {
1061 				write_rx_mon_char(curr_ch);
1062 				if (b_bot)
1063 				  {
1064 				    char ztbuf[20];
1065 				    struct timeval tv;
1066 				    gettimeofday(&tv,NULL);
1067 				    struct tm tm;
1068 				    time_t t_temp;
1069 				    t_temp=(time_t)tv.tv_sec;
1070 				    gmtime_r(&t_temp, &tm);
1071 				    strftime(ztbuf,sizeof(ztbuf),"%m/%d %H:%M:%S ",&tm);
1072 				    display_fsq_mon_text( ztbuf, FTextBase::CTRL);
1073 				    display_fsq_mon_text( fsq_bot, FTextBase::CTRL);
1074 				  }
1075 				if (b_eol) {
1076 					display_fsq_mon_text( fsq_eol, FTextBase::CTRL);
1077 					noisefilt->reset();
1078 					noisefilt->run(1);
1079 					sigfilt->reset();
1080 					sigfilt->run(1);
1081 					snprintf(szestimate, sizeof(szestimate), "%.0f db", s2n );
1082 				}
1083 				if (b_eot) {
1084 					snprintf(szestimate, sizeof(szestimate), "%.0f db", s2n );
1085 					noisefilt->reset();
1086 					noisefilt->run(1);
1087 					sigfilt->reset();
1088 					sigfilt->run(1);
1089 					display_fsq_mon_text( fsq_eot, FTextBase::CTRL);
1090 				}
1091 			}
1092 
1093 			if ( valid_char(curr_ch) || b_eol || b_eot ) {
1094 				if (rx_text.length() > 32768) rx_text.clear();
1095 				if ( fsq_squelch_open() || !progStatus.sqlonoff ) {
1096 					rx_text += curr_ch;
1097 					if (b_eot) {
1098 						parse_rx_text();
1099 						if (state == TEXT)
1100 							ch_sqlch_open = false;
1101 					}
1102 				}
1103 			}
1104 			if (fsq_squelch_open() && (b_eot || b_eol)) {
1105 				ch_sqlch_open = false;
1106 			}
1107 		}
1108 		prev_nibble = curr_nibble;
1109 	}
1110 
1111 	prev_symbol = symbol;
1112 }
1113 
1114 // find the maximum bin
1115 // 908 Hz and 1351 Hz respectively for original center frequency of 1145 Hz
1116 // 1280 to 1720 for a 1500 Hz center frequency
1117 
process_tones()1118 void fsq::process_tones()
1119 {
1120 	max = 0;
1121 	peak = NUMBINS / 2;
1122 
1123 // examine FFT bin contents over bandwidth +/- ~ 50 Hz
1124 	int firstbin = frequency * FSQ_SYMLEN / samplerate - NUMBINS / 2;
1125 
1126 	double sigval = 0;
1127 
1128 	double min = 3.0e8;
1129     int minbin = NUMBINS / 2;
1130 
1131 	for (int i = 0; i < NUMBINS; ++i) {
1132 		val = norm(fft_data[i + firstbin]);
1133 // looking for maximum signal
1134 		tones[i] = binfilt[i]->run(val);
1135 		if (tones[i] > max) {
1136 			max = tones[i];
1137 			peak = i;
1138 		}
1139 // looking for minimum signal in a 3 bin sequence
1140         if (tones[i] < min) {
1141             min = tones[i];
1142             minbin = i;
1143         }
1144 	}
1145 
1146 	sigval = tones[(peak-1) < 0 ? (NUMBINS - 1) : (peak - 1)] +
1147              tones[peak] +
1148              tones[(peak+1) == NUMBINS ? 0 : (peak + 1)];
1149 
1150 	min = tones[(minbin-1) < 0 ? (NUMBINS - 1) : (minbin - 1)] +
1151           tones[minbin] +
1152           tones[(minbin+1) == NUMBINS ? 0 : (minbin + 1)];
1153 
1154 	if (min == 0) min = 1e-10;
1155 
1156 	s2n = 10 * log10( snfilt->run(sigval/min)) - 34.0 + movavg_size / 4;
1157 
1158 	if (s2n <= 0) metric = 2 * (25 + s2n);
1159 	if (s2n > 0) metric = 50 * ( 1 + s2n / 45);
1160 	metric = clamp(metric, 0, 100);
1161 
1162 	display_metric(metric);
1163 
1164 	if (metric < progStatus.sldrSquelchValue && ch_sqlch_open)
1165 		ch_sqlch_open = false;
1166 
1167 // requires consecutive hits
1168 	if (peak == prev_peak) {
1169 		peak_counter++;
1170 	} else {
1171 		peak_counter = 0;
1172 	}
1173 
1174 	if ((peak_counter >= peak_hits) &&
1175 		(peak != last_peak) &&
1176 		(fsq_squelch_open() || !progStatus.sqlonoff)) {
1177 		process_symbol(peak);
1178 		peak_counter = 0;
1179 		last_peak = peak;
1180 	}
1181 
1182 	prev_peak = peak;
1183 }
1184 
recvpic(double smpl)1185 void fsq::recvpic(double smpl)
1186 {
1187 	phase -= phidiff;
1188 	if (phase < 0) phase += 2.0 * M_PI;
1189 
1190 	cmplx z = smpl * cmplx( cos(phase), sin(phase ) );
1191 	picfilter->run( z, currz);
1192 	pixel += arg(conj(prevz) * currz);// * samplerate / TWOPI;
1193 	amplitude += norm(currz);
1194 	prevz = currz;
1195 
1196 	if (image_counter <= -RXspp) {
1197 		pixel = 0;
1198 		amplitude = 0;
1199 		image_counter++;
1200 		return;
1201 	}
1202 
1203 	if ((image_counter++ % RXspp) == 0) {
1204 
1205 		amplitude /= RXspp;
1206 		pixel /= RXspp;
1207 		pixel *= (samplerate / TWOPI);
1208 		byte = pixel / 1.5 + 128;
1209 		byte = (int)CLAMP( byte, 0.0, 255.0);
1210 
1211 // FSQCAL sends blue-green-red
1212 		static int RGB[] = {2, 1, 0};
1213 
1214 		if (image_mode == 2 || image_mode == 5 || image_mode == 7) { // grey scale
1215 			pixelnbr = 3 * (col + row * picW);
1216 			REQ(fsq_updateRxPic, byte, pixelnbr);
1217 			REQ(fsq_updateRxPic, byte, pixelnbr + 1);
1218 			REQ(fsq_updateRxPic, byte, pixelnbr + 2);
1219 			if (++ col == picW) {
1220 				col = 0;
1221 				row++;
1222 				if (row >= picH) {
1223 					state = TEXT;
1224 					REQ(fsq_enableshift);
1225 					metric = 0;
1226 				}
1227 			}
1228 		} else {
1229 			pixelnbr = RGB[rgb] + 3 * (col + row * picW);
1230 			REQ(fsq_updateRxPic, byte, pixelnbr);
1231 			if (++col == picW) {
1232 				col = 0;
1233 				if (++rgb == 3) {
1234 					rgb = 0;
1235 					row ++;
1236 				}
1237 			}
1238 			if (row >= picH) {
1239 				state = TEXT;
1240 				REQ(fsq_enableshift);
1241 				metric = 0;
1242 			}
1243 		}
1244 
1245 		pixel = 0;
1246 		amplitude = 0;
1247 	}
1248 }
1249 
rx_process(const double * buf,int len)1250 int fsq::rx_process(const double *buf, int len)
1251 {
1252 	if (peak_hits != progdefaults.fsqhits) restart();
1253 	if (movavg_size != progdefaults.fsq_movavg) restart();
1254 	if (speed != progdefaults.fsqbaud) restart();
1255 
1256 	if (enable_heard_log != progdefaults.fsq_enable_heard_log ||
1257 		enable_audit_log != progdefaults.fsq_enable_audit_log)
1258 		toggle_logs();
1259 
1260 	if (sounder_interval != progdefaults.fsq_sounder) {
1261 		sounder_interval = progdefaults.fsq_sounder;
1262 		start_sounder(sounder_interval);
1263 	}
1264 
1265 	if (bkptr < 0) bkptr = 0;
1266 	if (bkptr >= SHIFT_SIZE) bkptr = 0;
1267 
1268 	if (len > 512) {
1269 		LOG_ERROR("fsq rx stream overrun %d", len);
1270 	}
1271 
1272 	if (progStatus.fsq_rx_abort) {
1273 		state = TEXT;
1274 		progStatus.fsq_rx_abort = false;
1275 		REQ(fsq_clear_rximage);
1276 	}
1277 
1278 	while (len) {
1279 		if (state == IMAGE) {
1280 			recvpic(*buf);
1281 			len--;
1282 			buf++;
1283 		} else {
1284 			rx_stream[BLOCK_SIZE + bkptr] = *buf;
1285 			len--;
1286 			buf++;
1287 			bkptr++;
1288 
1289 			if (bkptr == SHIFT_SIZE) {
1290 				bkptr = 0;
1291 				memmove(rx_stream,							// to
1292 						&rx_stream[SHIFT_SIZE],				// from
1293 						BLOCK_SIZE*sizeof(*rx_stream));	// # bytes
1294                 // fft_data gets overwritten each time with a fixed number of
1295                 // elements. Do we need to zero it out?
1296 				//memset(fft_data, 0, sizeof(fft_data));
1297 				for (int i = 0; i < BLOCK_SIZE; i++) {
1298 					double d = rx_stream[i] * a_blackman[i];
1299 					fft_data[i] = cmplx(d, d);
1300 				}
1301 				fft->ComplexFFT(fft_data);
1302 				process_tones();
1303 			}
1304 		}
1305 	}
1306 	return 0;
1307 }
1308 
1309 //=====================================================================
1310 // transmit processing
1311 //=====================================================================
1312 
1313 // implement the symbol counter using a new thread whose thread loop
1314 // time is equal to a symbol length 4096/12000 = 341 milliseconds
1315 // symbol loop decrements symbol counts and send_symbol increments them
1316 // flush_buffer will then awake for the symbol count to be zero
1317 // have not observed a remaining count > 1 so this might be an over kill!
1318 // typical awake period is 90 msec
1319 
flush_buffer()1320 void fsq::flush_buffer()
1321 {
1322 	for (int i = 0; i < 64; i++) outbuf[i] = 0;
1323 	ModulateXmtr(outbuf, 64);
1324 	return;
1325 }
1326 
1327 #include "confdialog.h"
1328 
send_tone(int tone)1329 void fsq::send_tone(int tone)
1330 {
1331 	double phaseincr;
1332 	double freq;
1333 
1334 	if (speed != progdefaults.fsqbaud) restart();
1335 
1336 	freq = (tx_basetone + tone * spacing) * samplerate / FSQ_SYMLEN;
1337 	if (test_signal_window && test_signal_window->visible() && btnOffsetOn->value())
1338 		freq += ctrl_freq_offset->value();
1339 
1340 	phaseincr = 2.0 * M_PI * freq / samplerate;
1341 	prevtone = tone;
1342 
1343 	int send_symlen = symlen;
1344 	if (fsq_tx_image) send_symlen = 4096; // must use 3 baud symlen for image xfrs
1345 
1346 	for (int i = 0; i < send_symlen; i++) {
1347 		outbuf[i] = cos(txphase);
1348 		txphase -= phaseincr;
1349 		if (txphase < 0) txphase += TWOPI;
1350 	}
1351 	ModulateXmtr(outbuf, send_symlen);
1352 }
1353 
send_symbol(int sym)1354 void fsq::send_symbol(int sym)
1355 {
1356 
1357 	tone = (prevtone + sym + 1) % 33;
1358 	send_tone(tone);
1359 }
1360 
send_idle()1361 void fsq::send_idle()
1362 {
1363 	send_symbol(28);
1364 	send_symbol(30);
1365 }
1366 
1367 static bool send_eot = false;
1368 
send_char(int ch)1369 void fsq::send_char(int ch)
1370 {
1371 	if (!ch) return send_idle();
1372 
1373 	int sym1 = fsq_varicode[ch][0];
1374 	int sym2 = fsq_varicode[ch][1];
1375 
1376 	send_symbol(sym1);
1377 	if (sym2 > 28)
1378 		send_symbol(sym2);
1379 
1380 	if (valid_char(ch) && !(send_bot || send_eot))
1381 		put_echo_char(ch);
1382 
1383 	write_mon_tx_char(ch);
1384 }
1385 
send_image()1386 void fsq::send_image()
1387 {
1388 	int W = 640, H = 480;  // grey scale transfer (FAX)
1389 	bool color = true;
1390 	float freq, phaseincr;
1391 	float radians = 2.0 * M_PI / samplerate;
1392 
1393 	if (!fsqpicTxWin || !fsqpicTxWin->visible()) {
1394 		return;
1395 	}
1396 
1397 	switch (selfsqpicSize->value()) {
1398 		case 0 : W = 160; H = 120; break;
1399 		case 1 : W = 320; H = 240; break;
1400 		case 2 : W = 640; H = 480; color = false; break;
1401 		case 3 : W = 640; H = 480; break;
1402 		case 4 : W = 240; H = 300; break;
1403 		case 5 : W = 240; H = 300; color = false; break;
1404 		case 6 : W = 120; H = 150; break;
1405 		case 7 : W = 120; H = 150; color = false; break;
1406 	}
1407 
1408 	REQ(fsq_clear_tximage);
1409 
1410 	stop_deadman();
1411 
1412 	freq = frequency - 200;
1413 	#define PHASE_CORR  200
1414 	phaseincr = radians * freq;
1415 	for (int n = 0; n < PHASE_CORR; n++) {
1416 		outbuf[n] = cos(txphase);
1417 		txphase -= phaseincr;
1418 		if (txphase < 0) txphase += TWOPI;
1419 	}
1420 	ModulateXmtr(outbuf, 10);
1421 
1422 	if (color == false) {  // grey scale image
1423 		for (int row = 0; row < H; row++) {
1424 			memset(outbuf, 0, 10 * sizeof(*outbuf));
1425 			for (int col = 0; col < W; col++) {
1426 				if (stopflag) return;
1427 				tx_pixelnbr = col + row * W;
1428 				tx_pixel =	0.3 * fsqpic_TxGetPixel(tx_pixelnbr, 0) +   // red
1429 							0.6 * fsqpic_TxGetPixel(tx_pixelnbr, 1) +   // green
1430 							0.1 * fsqpic_TxGetPixel(tx_pixelnbr, 2);    // blue
1431 				REQ(fsq_updateTxPic, tx_pixel, tx_pixelnbr*3 + 0);
1432 				REQ(fsq_updateTxPic, tx_pixel, tx_pixelnbr*3 + 1);
1433 				REQ(fsq_updateTxPic, tx_pixel, tx_pixelnbr*3 + 2);
1434 				freq = frequency - 200 + tx_pixel * 1.5;
1435 				phaseincr = radians * freq;
1436 				for (int n = 0; n < 10; n++) {
1437 					outbuf[n] = cos(txphase);
1438 					txphase -= phaseincr;
1439 					if (txphase < 0) txphase += TWOPI;
1440 				}
1441 				ModulateXmtr(outbuf, 10);
1442 				Fl::awake();
1443 			}
1444 		}
1445 	} else {
1446 		for (int row = 0; row < H; row++) {
1447 			for (int color = 2; color >= 0; color--) {
1448 				memset(outbuf, 0, 10 * sizeof(*outbuf));
1449 				for (int col = 0; col < W; col++) {
1450 					if (stopflag) return;
1451 					tx_pixelnbr = col + row * W;
1452 					tx_pixel = fsqpic_TxGetPixel(tx_pixelnbr, color);
1453 					REQ(fsq_updateTxPic, tx_pixel, tx_pixelnbr*3 + color);
1454 					freq = frequency - 200 + tx_pixel * 1.5;
1455 					phaseincr = radians * freq;
1456 					for (int n = 0; n < 10; n++) {
1457 						outbuf[n] = cos(txphase);
1458 						txphase -= phaseincr;
1459 						if (txphase < 0) txphase += TWOPI;
1460 					}
1461 					ModulateXmtr(outbuf, 10);
1462 				}
1463 				Fl::awake();
1464 			}
1465 		}
1466 	}
1467 	start_deadman();
1468 }
1469 
send_string(std::string s)1470 void fsq::send_string(std::string s)
1471 {
1472 	for (size_t n = 0; n < s.length(); n++)
1473 		send_char(s[n]);
1474 	if ((s == FSQEOT || s == FSQEOL) && fsq_tx_image) send_image();
1475 }
1476 
fsq_send_image(std::string s)1477 void fsq::fsq_send_image(std::string s) {
1478 	fsq_tx_image = true;
1479 	fsq_string = std::string("IMAGE:").append(s);
1480 	write_fsq_que(fsq_string);
1481 	fsq_xmt(s);
1482 }
1483 
tx_process()1484 int fsq::tx_process()
1485 {
1486 	modem::tx_process();
1487 
1488 	if (send_bot) {
1489 		std::string send;
1490 		send.assign(" ").append(FSQBOL).append(mycall).append(":");
1491 		if (progdefaults.fsq_directed)
1492 			send.append(crc.sval(mycall));
1493 		send_string(send);
1494 		send_bot = false;
1495 	}
1496 	int c = get_tx_char();
1497 
1498 	if (c == GET_TX_CHAR_ETX  || c == -1) { // end of text or empty tx buffer
1499 		send_eot = true;
1500 		if (progdefaults.fsq_directed)
1501 			send_string(std::string(FSQEOT));
1502 		else
1503 			send_string(std::string(FSQEOL));
1504 		send_eot = false;
1505 		put_echo_char('\n');
1506 		if (c == -1) REQ(&FTextTX::clear, TransmitText);
1507 		flush_buffer();
1508 		stopflag = false;
1509 		fsq_tx_image = false;
1510 		return -1;
1511 	}
1512 	if ( stopflag ) { // aborts transmission
1513 		static std::string aborted = " !ABORTED!\n";
1514 		for (size_t n = 0; n < aborted.length(); n++)
1515 			put_echo_char(aborted[n]);
1516 		fsq_tx_image = false;
1517 		stopflag = false;
1518 		clear_xmt_arrays();
1519 		return -1;
1520 	}
1521 	send_char(c);
1522 	return 0;
1523 }
1524 
1525 //==============================================================================
1526 // delayed transmit
1527 //==============================================================================
1528 
1529 static pthread_mutex_t fsq_tx_mutex = PTHREAD_MUTEX_INITIALIZER;
1530 static float xmt_repeat_try = 6.0;
1531 static string tx_text_queue = "";
1532 
1533 static vector<string> commands;
1534 #define NUMCOMMANDS 10
1535 static size_t next = 0;
1536 
clear_xmt_arrays()1537 void  clear_xmt_arrays()
1538 {
1539 	commands.erase(commands.begin(), commands.begin());
1540 	tx_text_queue.clear();
1541 	fsq_tx_text->clear();
1542 }
1543 
fsq_xmtdelay()1544 double fsq_xmtdelay() // in seconds
1545 {
1546 #define MIN_DELAY  50
1547 #define MAX_DELAY  500
1548 	srand((int)clock());
1549 	double scaled = (double)rand()/RAND_MAX;
1550 	double delay = ((MAX_DELAY - MIN_DELAY + 1 ) * scaled + MIN_DELAY) / 1000.0;
1551 	if (delay < 0.05) delay = 0.05;
1552 	if (delay > 0.5) delay = 0.5;
1553 	return delay;
1554 }
1555 
fsq_repeat_last_command()1556 void fsq_repeat_last_command()
1557 {
1558 	using ::next;  // disambiguate from std::next
1559 	if (commands.empty()) return;
1560 	fsq_tx_text->clear();
1561 	fsq_tx_text->addstr(sz2utf8(commands[next].c_str()));
1562 	next++;
1563 	if (next == commands.size()) {
1564 		next = 0;
1565 	}
1566 }
1567 
get_fsq_tx_char(void)1568 int get_fsq_tx_char(void)
1569 {
1570 	guard_lock tx_proc_lock(&fsq_tx_mutex);
1571 
1572 	if (tx_text_queue.empty()) return (GET_TX_CHAR_NODATA);
1573 
1574 	int c = tx_text_queue[0];
1575 	tx_text_queue.erase(0,1);
1576 
1577 	if (c == GET_TX_CHAR_ETX) {
1578 		return c;
1579 	}
1580 	if (c == -1)
1581 		return(GET_TX_CHAR_NODATA);
1582 
1583 	if (c == '^') {
1584 		c = tx_text_queue[0];
1585 		tx_text_queue.erase(0,1);
1586 
1587 		if (c == -1) return(GET_TX_CHAR_NODATA);
1588 
1589 		switch (c) {
1590 			case 'r':
1591 				return(GET_TX_CHAR_ETX);
1592 				break;
1593 			case 'R':
1594 				return(GET_TX_CHAR_ETX);
1595 				break;
1596 			default: ;
1597 		}
1598 	}
1599 	start_deadman();
1600 	return(c);
1601 }
1602 
try_transmit(void *)1603 void try_transmit(void *)
1604 {
1605 	if (active_modem != fsq_modem) return;
1606 
1607 	if (!active_modem->fsq_squelch_open() && trx_state == STATE_RX) {
1608 		::next = 0;
1609 		fsq_que_clear();
1610 //LOG_WARN("%s", "start_tx()");
1611 		start_tx();
1612 		return;
1613 	}
1614 
1615 	if (xmt_repeat_try > 0) {
1616 		xmt_repeat_try -= 0.5;
1617 		static char szwaiting[50];
1618 		snprintf(szwaiting, sizeof(szwaiting), "Waiting %4.2f", xmt_repeat_try);
1619 		fsq_que_clear();
1620 		write_fsq_que(std::string(szwaiting).append("\n").append(fsq_string));
1621 //LOG_WARN("%s", szwaiting);
1622 		Fl::repeat_timeout(0.5, try_transmit);
1623 		return;
1624 	} else {
1625 		static const char szsquelch[50] = "Squelch open.  Transmit timed out!";
1626 		display_fsq_rx_text(string("\n").append(szsquelch).append("\n").c_str(), FTextBase::ALTR);
1627 		tx_text_queue.clear();
1628 		fsq_que_clear();
1629 		if (active_modem->fsq_tx_image) active_modem->fsq_tx_image = false;
1630 //LOG_WARN("%s", szsquelch);
1631 		return;
1632 	}
1633 	return;
1634 }
1635 
_fsq_xmt(string s)1636 inline void _fsq_xmt(string s)
1637 {
1638 	tx_text_queue.clear();
1639 	if (commands.size() > NUMCOMMANDS)
1640 		commands.pop_back();
1641 
1642 	commands.insert(commands.begin(), 1, s);
1643 	s.append("^r");
1644 	tx_text_queue = s;
1645 
1646 	xmt_repeat_try = progdefaults.fsq_time_out;
1647 	Fl::add_timeout(0.5 + fsq_xmtdelay(), try_transmit);
1648 }
1649 
fsq_xmt_mt(void * cs=(void *)0)1650 void fsq_xmt_mt(void *cs = (void *)0)
1651 {
1652 	guard_lock tx_proc_lock(&fsq_tx_mutex);
1653 
1654 	if (active_modem != fsq_modem) return;
1655     if (!cs) return;
1656 
1657 	string s;
1658 	s.assign((char *) cs);
1659 	delete (char *) cs;
1660 
1661 	if(!s.empty()) {
1662 		_fsq_xmt(s);
1663     }
1664 }
1665 
fsq_xmt(string s)1666 void fsq_xmt(string s)
1667 {
1668 	guard_lock tx_proc_lock(&fsq_tx_mutex);
1669 
1670 	if (active_modem != fsq_modem) return;
1671 
1672 	if(!s.empty()) {
1673 		_fsq_xmt(s);
1674     }
1675 }
1676 
fsq_transmit(void * a=(void *)0)1677 void fsq_transmit(void *a = (void *)0)
1678 {
1679 	guard_lock tx_proc_lock(&fsq_tx_mutex);
1680 
1681 	if (active_modem != fsq_modem) return;
1682 
1683 	if (!tx_text_queue.empty()) {
1684 		size_t p = tx_text_queue.find("^r");
1685 		tx_text_queue.erase(p);
1686 		tx_text_queue += ' ';
1687 		int nxt = fsq_tx_text->nextChar();
1688 		while (nxt != -1) {
1689 			tx_text_queue += nxt;
1690 			nxt = fsq_tx_text->nextChar();
1691 		}
1692 		commands.erase(commands.begin(), commands.begin());
1693 		commands.insert(commands.begin(), 1, tx_text_queue);
1694 		tx_text_queue.append("^r");
1695 		fsq_tx_text->clear();
1696 		return;
1697 //LOG_WARN("A: %s", tx_text_queue.c_str());
1698 	}
1699 
1700 	int nxt = fsq_tx_text->nextChar();
1701 	while (nxt != -1) {
1702 		tx_text_queue += nxt;
1703 		nxt = fsq_tx_text->nextChar();
1704 	}
1705 	if (commands.size() > NUMCOMMANDS)
1706 		commands.pop_back();
1707 	commands.insert(commands.begin(), 1, tx_text_queue);
1708 	tx_text_queue.append("^r");
1709 	fsq_tx_text->clear();
1710 //LOG_WARN("B: %s", tx_text_queue.c_str());
1711 
1712 	xmt_repeat_try = progdefaults.fsq_time_out;
1713 	Fl::add_timeout(0.5 + fsq_xmtdelay(), try_transmit);
1714 }
1715 
timed_xmt(void *)1716 void timed_xmt(void *)
1717 {
1718 //LOG_WARN("%s", fsq_delayed_string.c_str());
1719 	fsq_xmt(fsq_delayed_string);
1720 }
1721 
1722 static float secs = 0;
1723 
fsq_add_tx_timeout(void * a=0)1724 void fsq_add_tx_timeout(void *a = 0)
1725 {
1726 	Fl::add_timeout(secs, timed_xmt);
1727 }
1728 
reply(std::string s)1729 void fsq::reply(std::string s)
1730 {
1731 	fsq_string = std::string("SEND: ").append(s);
1732 	write_fsq_que(fsq_string);
1733 	char *cs = (char *)0;
1734 	cs = new char[s.size() + 1];
1735     if(!cs) return;
1736 	cs[s.size()] = 0;
1737 	memcpy(cs, s.c_str(), s.size());
1738 	Fl::awake(fsq_xmt_mt, (void *) cs);
1739 }
1740 
delayed_reply(std::string s,int delay)1741 void fsq::delayed_reply(std::string s, int delay)
1742 {
1743 	fsq_string = std::string("DELAYED SEND: ").append(s);
1744 	write_fsq_que(fsq_string);
1745 	fsq_delayed_string = s;
1746 	secs = delay;
1747 	Fl::awake(fsq_add_tx_timeout, 0);
1748 //LOG_WARN("%s : %d", s.c_str(), delay);
1749 }
1750 
1751 //==============================================================================
1752 // Heard list aging
1753 //==============================================================================
aging(void * who)1754 void aging(void *who)
1755 {
1756 	fsq *me = (fsq *)who;
1757 	if (me != active_modem) return;
1758 	age_heard_list();
1759 	Fl::repeat_timeout(60.0, aging, me);
1760 }
1761 
fsq_start_aging(void * who)1762 void fsq_start_aging(void *who)
1763 {
1764 	fsq	*me = (fsq *)who;
1765 	Fl::remove_timeout(aging);
1766 	Fl::add_timeout(60.0, aging, me);
1767 }
1768 
start_aging()1769 void fsq::start_aging()
1770 {
1771 	Fl::awake(fsq_start_aging, this);
1772 }
1773 
fsq_stop_aging(void *)1774 void fsq_stop_aging(void *)
1775 {
1776 	Fl::remove_timeout(aging);
1777 }
1778 
stop_aging()1779 void fsq::stop_aging()
1780 {
1781 	Fl::awake(fsq_stop_aging);
1782 }
1783 
1784 //==============================================================================
1785 // Sounder support
1786 //==============================================================================
1787 static int sounder_tries = 10;
1788 static double sounder_secs = 60;
1789 
sounder(void *)1790 void sounder(void *)
1791 {
1792 	if (active_modem != fsq_modem) return;
1793 
1794 	if (trx_state == STATE_TX) {
1795 		Fl::repeat_timeout(active_modem->fsq_xmtdelay(), timed_xmt);
1796 		return;
1797 	}
1798 	if (active_modem->fsq_squelch_open()) {
1799 		if (--sounder_tries < 0) {
1800 			display_fsq_rx_text("\nSounder timed out!\n", FTextBase::ALTR);
1801 			sounder_tries = 10;
1802 			Fl::repeat_timeout(sounder_secs, sounder);
1803 			return;
1804 		}
1805 		Fl::repeat_timeout(10, sounder); // retry in 10 seconds
1806 		return;
1807 	}
1808 	sounder_tries = 10;
1809 	std::string xmtstr = FSQBOL;
1810 	xmtstr.append(active_modem->fsq_mycall()).append(":").append(FSQEOT);
1811 	int numsymbols = xmtstr.length();
1812 
1813 	int xmtsecs = (int)(1.0 * numsymbols * (fsq::symlen / 4096.0) / SR);
1814 
1815 	if (fsq_tx_text->eot()) {
1816 		std::string stime = ztime();
1817 		stime.erase(4);
1818 		stime.insert(2,":");
1819 		std::string sndx = "Sounded @ ";
1820 		sndx.append(stime);
1821 		display_fsq_rx_text(sndx, FTextBase::ALTR);
1822 
1823 		fsq_xmt(" ");
1824 	}
1825 
1826 	Fl::repeat_timeout(sounder_secs - xmtsecs, sounder);
1827 }
1828 
fsq_start_sounder()1829 void fsq_start_sounder()
1830 {
1831 	if (active_modem != fsq_modem) return;
1832 	Fl::remove_timeout(sounder);
1833 	Fl::add_timeout(sounder_secs, sounder);
1834 }
1835 
fsq_stop_sounder()1836 void fsq_stop_sounder()
1837 {
1838 	Fl::remove_timeout(sounder);
1839 }
1840 
stop_sounder()1841 void fsq::stop_sounder()
1842 {
1843 	REQ(fsq_stop_sounder);
1844 }
1845 
start_sounder(int interval)1846 void fsq::start_sounder(int interval)
1847 {
1848 	if (interval == 0) {
1849 		REQ(fsq_stop_sounder);
1850 		return;
1851 	}
1852 	switch (interval) {
1853 		case 0: return;
1854 		case 1: sounder_secs = 60; break;   // 1 minute
1855 		case 2: sounder_secs = 600; break;  // 10 minutes
1856 		case 3: sounder_secs = 1800; break; // 30 minutes
1857 		case 4: sounder_secs = 3600; break; // 60 minutes
1858 		default: sounder_secs = 600;
1859 	}
1860 	REQ(fsq_start_sounder);
1861 }
1862 
1863 #include "bitmaps.cxx"
1864