1 // ----------------------------------------------------------------------------
2 // ifkp.cxx  --  ifkp 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 <libgen.h>
33 
34 #include <FL/filename.H>
35 #include "progress.h"
36 #include "ifkp.h"
37 #include "complex.h"
38 #include "fl_digi.h"
39 #include "ascii.h"
40 #include "misc.h"
41 #include "fileselect.h"
42 #include "threads.h"
43 #include "debug.h"
44 
45 #include "configuration.h"
46 #include "qrunner.h"
47 #include "fl_digi.h"
48 #include "status.h"
49 #include "main.h"
50 #include "icons.h"
51 
52 #include "confdialog.h"
53 
54 #include "test_signal.h"
55 
56 using namespace std;
57 
58 #include "ifkp_varicode.cxx"
59 
60 #define IFKP_SR 16000
61 
62 #include "ifkp-pic.cxx"
63 
64 static fre_t call("([[:alnum:]]?[[:alpha:]/]+[[:digit:]]+[[:alnum:]/]+)", REG_EXTENDED);
65 static string teststr = "";
66 static string allowed = " 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/";
67 static char sz[21];
68 
69 static bool enable_audit_log = false;
70 static bool enable_heard_log = false;
71 
72 int ifkp::IMAGEspp = IMAGESPP;
73 
valid_callsign(char ch)74 static string valid_callsign(char ch)
75 {
76 	if (allowed.find(ch) == std::string::npos) ch = ' ';
77 	teststr += tolower(ch);
78 	if (teststr.length() > 20) teststr.erase(0,1);
79 	// wait for ' de '
80 	size_t p1;
81 	if ((p1 = teststr.find(" de ")) != std::string::npos) { // test for callsign
82 		p1 += 4;
83 		if (p1 >= teststr.length()) return "";
84 		while (p1 < teststr.length() && teststr[p1] == ' ') p1++;
85 		if (p1 == teststr.length()) return "";
86 		size_t p2 = teststr.rfind(' ');
87 		if ((p2 > p1) && (p2 - p1 < 21)) { // found a word, test for callsign
88 			memset(sz, 0, 21);
89 			strcpy(sz, teststr.substr(p1, p2-p1).c_str());
90 			teststr.erase(0, p2);
91 			if (call.match(sz)) {
92 				return sz;
93 			}
94 			return "";
95 		}
96 	}
97 	return "";
98 }
99 
100 // nibbles table used for fast conversion from tone difference to symbol
101 
init_nibbles()102 void ifkp::init_nibbles()
103 {
104 	int nibble = 0;
105 	for (int i = 0; i < 199; i++) {
106 		nibble = floor(0.5 + (i - 99.0)/IFKP_SPACING);
107 		// allow for wrap-around (33 tones for 32 tone differences)
108 		if (nibble < 0) nibble += 33;
109 		if (nibble > 32) nibble -= 33;
110 		// adjust for +1 symbol at the transmitter
111 		nibble--;
112 		nibbles[i] = nibble;
113 	}
114 }
115 
ifkp(trx_mode md)116 ifkp::ifkp(trx_mode md) : modem()
117 {
118 	samplerate = IFKP_SR;
119 	symlen = IFKP_SYMLEN;
120 
121 	cap |= CAP_IMG;
122 
123 	if (progdefaults.StartAtSweetSpot) {
124 		frequency = progdefaults.PSKsweetspot;
125 	} else
126 		frequency = wf->Carrier();
127 	REQ(put_freq, frequency);
128 
129 	mode = md;
130 	fft = new g_fft<double>(IFKP_FFTSIZE);
131 	snfilt = new Cmovavg(64);
132 	movavg_size = 4;
133 	for (int i = 0; i < IFKP_NUMBINS; i++) binfilt[i] = new Cmovavg(movavg_size);
134 	txphase = 0;
135 	basetone = 197;
136 
137 	float lo = frequency - 0.75 * bandwidth;
138 	float hi = frequency + 0.75 * bandwidth;
139 
140 	rxfilter = new C_FIR_filter();
141 	rxfilter->init_bandpass(129, 1, lo/samplerate, hi/samplerate);
142 
143 	picfilter = new C_FIR_filter();
144 	picfilter->init_lowpass(129, 1, 2.0 * bandwidth / samplerate);
145 
146 	phase = 0;
147 	phidiff = 2.0 * M_PI * frequency / samplerate;
148 
149 	IMAGEspp = IMAGESPP;
150 	pixfilter = new Cmovavg(IMAGEspp / 2);
151 	ampfilter = new Cmovavg(IMAGEspp);
152 	syncfilter = new Cmovavg(IMAGEspp / 2);
153 
154 	bkptr = 0;
155 	peak_counter = 0;
156 	peak = last_peak = 0;
157 	max = 0;
158 	curr_nibble = prev_nibble = 0;
159 	s2n = 0;
160 
161 	memset(rx_stream, 0, sizeof(rx_stream));
162 	rx_text.clear();
163 
164 	for (int i = 0; i < IFKP_BLOCK_SIZE; i++)
165 		a_blackman[i] = blackman(1.0 * i / IFKP_BLOCK_SIZE);
166 
167 	state = TEXT;
168 
169 	init_nibbles();
170 
171 	TX_IMAGE = TX_AVATAR = false;
172 
173 	restart();
174 
175 	toggle_logs();
176 
177 	activate_ifkp_image_item(true);
178 
179 }
180 
~ifkp()181 ifkp::~ifkp()
182 {
183 	delete fft;
184 	delete snfilt;
185 	delete rxfilter;
186 	delete picfilter;
187 	for (int i = 0; i < IFKP_NUMBINS; i++)
188 		delete binfilt[i];
189 	ifkp_deleteTxViewer();
190 	ifkp_deleteRxViewer();
191 	heard_log.close();
192 	audit_log.close();
193 
194 	activate_ifkp_image_item(false);
195 
196 }
197 
tx_init()198 void  ifkp::tx_init()
199 {
200 	tone = prevtone = 0;
201 	txphase = 0;
202 	send_bot = true;
203 	mycall = progdefaults.myCall;
204 	if (progdefaults.ifkp_lowercase)
205 		for (size_t n = 0; n < mycall.length(); n++) mycall[n] = tolower(mycall[n]);
206 	videoText();
207 }
208 
rx_init()209 void  ifkp::rx_init()
210 {
211 	bkptr = 0;
212 	peak_counter = 0;
213 	peak = last_peak = 0;
214 	max = 0;
215 	curr_nibble = prev_nibble = 0;
216 	s2n = 0;
217 
218 	memset(rx_stream, 0, sizeof(rx_stream));
219 
220 	prevz = cmplx(0,0);
221 	image_counter = 0;
222 	state = TEXT;
223 
224 	rx_text.clear();
225 	for (int i = 0; i < IFKP_NUMBINS; i++) {
226 		tones[i] = 0.0;
227 		binfilt[i]->reset();
228 	}
229 	pixel = 0;
230 	pic_str = "     ";
231 }
232 
rx_reset()233 void ifkp::rx_reset()
234 {
235 	prevz = cmplx(0,0);
236 	image_counter = 0;
237 	pixel = 0;
238 	pic_str = "     ";
239 	snfilt->reset();
240 	state = TEXT;
241 }
242 
init()243 void ifkp::init()
244 {
245 	peak_hits = 4;
246 
247 	mycall = progdefaults.myCall;
248 	if (progdefaults.ifkp_lowercase)
249 		for (size_t n = 0; n < mycall.length(); n++) mycall[n] = tolower(mycall[n]);
250 
251 	movavg_size = 3;
252 
253 	for (int i = 0; i < IFKP_NUMBINS; i++) binfilt[i]->setLength(movavg_size);
254 
255 	rx_init();
256 }
257 
set_freq(double f)258 void ifkp::set_freq(double f)
259 {
260 	if (progdefaults.ifkp_freqlock)
261 		frequency = 1500;
262 	else
263 		frequency = f;
264 
265 	if (frequency < 100 + 0.5 * bandwidth) frequency = 100 + 0.5 * bandwidth;
266 	if (frequency > 3900 - 0.5 * bandwidth) frequency = 3900 - 0.5 * bandwidth;
267 
268 	tx_frequency = frequency;
269 
270 	REQ(put_freq, frequency);
271 
272 	set_bandwidth(33 * IFKP_SPACING * samplerate / symlen);
273 
274 	basetone = ceil((frequency - bandwidth / 2.0) * symlen / samplerate);
275 
276 	float lo = frequency - 0.75 * bandwidth;
277 	float hi = frequency + 0.75 * bandwidth;
278 
279 	rxfilter->init_bandpass(129, 1, lo/samplerate, hi/samplerate);
280 	picfilter->init_lowpass(129, 1, 2.0 * bandwidth / samplerate);
281 
282 	phase = 0;
283 	phidiff = 2.0 * M_PI * frequency / samplerate;
284 
285 	std::ostringstream it;
286 	it << "\nSamplerate..... " << samplerate;
287 	it << "\nBandwidth...... " << bandwidth;
288 	it << "\nlower cutoff... " << lo;
289 	it << "\nupper cutoff... " << hi;
290 	it << "\ncenter ........ " << frequency;
291 	it << "\nSymbol length.. " << symlen    << "\nBlock size..... " << IFKP_SHIFT_SIZE;
292 	it << "\nMinimum Hits... " << peak_hits << "\nBasetone....... " << basetone << "\n";
293 
294 	LOG_VERBOSE("%s", it.str().c_str());
295 }
296 
show_mode()297 void ifkp::show_mode()
298 {
299 	if (progdefaults.ifkp_baud == 0)
300 		put_MODEstatus("IFKP 0.5");
301 	else if (progdefaults.ifkp_baud == 1)
302 		put_MODEstatus("IFKP 1.0");
303 	else
304 		put_MODEstatus("IFKP 2.0");
305 	return;
306 }
307 
toggle_logs()308 void ifkp::toggle_logs()
309 {
310 	if (enable_audit_log != progdefaults.ifkp_enable_audit_log){
311 		enable_audit_log = progdefaults.ifkp_enable_audit_log;
312 		if (audit_log.is_open()) audit_log.close();
313 	}
314 	if (enable_heard_log != progdefaults.ifkp_enable_heard_log) {
315 		enable_heard_log = progdefaults.ifkp_enable_heard_log;
316 		if (heard_log.is_open()) heard_log.close();
317 	}
318 
319 	if (enable_heard_log) {
320 		heard_log_fname = progdefaults.ifkp_heard_log;
321 		std::string sheard = TempDir;
322 		sheard.append(heard_log_fname);
323 		heard_log.open(sheard.c_str(), ios::app);
324 
325 		heard_log << "==================================================\n";
326 		heard_log << "Heard log: " << zdate() << ", " << ztime() << "\n";
327 		heard_log << "==================================================\n";
328 		heard_log.flush();
329 	}
330 
331 	if (enable_audit_log) {
332 		audit_log_fname = progdefaults.ifkp_audit_log;
333 		std::string saudit = TempDir;
334 		saudit.append(audit_log_fname);
335 		audit_log.close();
336 		audit_log.open(saudit.c_str(), ios::app);
337 
338 		audit_log << "==================================================\n";
339 		audit_log << "Audit log: " << zdate() << ", " << ztime() << "\n";
340 		audit_log << "==================================================\n";
341 		audit_log.flush();
342 	}
343 
344 }
345 
restart()346 void ifkp::restart()
347 {
348 	set_freq(wf->Carrier());
349 
350 	peak_hits = 4;
351 
352 	mycall = progdefaults.myCall;
353 	if (progdefaults.ifkp_lowercase)
354 		for (size_t n = 0; n < mycall.length(); n++) mycall[n] = tolower(mycall[n]);
355 
356 	movavg_size = progdefaults.ifkp_baud == 2 ? 3 : 4;
357 
358 	for (int i = 0; i < IFKP_NUMBINS; i++) binfilt[i]->setLength(movavg_size);
359 
360 	show_mode();
361 
362 }
363 
364 // valid printable character
365 
valid_char(int ch)366 bool ifkp::valid_char(int ch)
367 {
368 	if ( ! (ch ==  10 || ch == 163 || ch == 176 ||
369 		ch == 177 || ch == 215 || ch == 247 ||
370 		(ch > 31 && ch < 128)))
371 		return false;
372 	return true;
373 }
374 
375 //=====================================================================
376 // receive processing
377 //=====================================================================
378 
parse_pic(int ch)379 void ifkp::parse_pic(int ch)
380 {
381 	b_ava = false;
382 	image_mode = 0;
383 
384 	pic_str.erase(0,1);
385 	pic_str += ch;
386 
387 	if (pic_str.find("pic%") == 0) {
388 		switch (pic_str[4]) {
389 			case 'A':	picW = 59; picH = 74; b_ava = true; break;
390 			case 'T':	picW = 59; picH = 74; break;
391 			case 't':	picW = 59; picH = 74; image_mode = 1; break;
392 			case 'S':	picW = 160; picH = 120; break;
393 			case 's':	picW = 160; picH = 120; image_mode = 1; break;
394 			case 'L':	picW = 320; picH = 240; break;
395 			case 'l':	picW = 320; picH = 240; image_mode = 1; break;
396 			case 'V':	picW = 640; picH = 480; break;
397 			case 'v':	picW = 640; picH = 480; image_mode = 1; break;
398 			case 'F':	picW = 640; picH = 480; image_mode = 1; break;
399 			case 'P':	picW = 240; picH = 300; break;
400 			case 'p':	picW = 240; picH = 300; image_mode = 1; break;
401 			case 'M':	picW = 120; picH = 150; break;
402 			case 'm':	picW = 120; picH = 150; image_mode = 1; break;
403 			default:
404 				syncfilter->reset();
405 				pixfilter->reset();
406 				ampfilter->reset();
407 				return;
408 		}
409 	} else
410 		return;
411 
412 	if (!b_ava)
413 		REQ( ifkp_showRxViewer, pic_str[4]);
414 	else
415 		REQ( ifkp_clear_avatar );
416 
417 	image_counter = -symlen / 2;
418 	col = row = rgb = 0;
419 	syncfilter->reset();
420 	pixfilter->reset();
421 //	ampfilter->reset();
422 	state = IMAGE_START;
423 }
424 
process_symbol(int sym)425 void ifkp::process_symbol(int sym)
426 {
427 	int nibble = 0;
428 	int curr_ch = -1	;
429 
430 	symbol = sym;
431 
432 	nibble = symbol - prev_symbol;
433 	if (nibble < -99 || nibble > 99) {
434 		prev_symbol = symbol;
435 		return;
436 	}
437 	nibble = nibbles[nibble + 99];
438 
439 	if (nibble >= 0) { // process nibble
440 		curr_nibble = nibble;
441 
442 // single-nibble characters
443 		if ((prev_nibble < 29) & (curr_nibble < 29)) {
444 			curr_ch = ifkp_varidecode[prev_nibble];
445 
446 // double-nibble characters
447 		} else if ( (prev_nibble < 29) &&
448 					 (curr_nibble > 28) &&
449 					 (curr_nibble < 32)) {
450 			curr_ch = ifkp_varidecode[prev_nibble * 32 + curr_nibble];
451 		}
452 		if (curr_ch > 0) {
453 			if (ch_sqlch_open || metric >= progStatus.sldrSquelchValue) {
454 //			if (metric >= progStatus.sldrSquelchValue) {
455 				put_rx_char(curr_ch, FTextBase::RECV);
456 				if (enable_audit_log) {
457 					audit_log << ifkp_ascii[curr_ch];
458 					if (curr_ch == '\n') audit_log << '\n';
459 				}
460 				parse_pic(curr_ch);
461 				if (state != IMAGE_START) {
462 				station_calling = valid_callsign(curr_ch);
463 				if (!station_calling.empty()) {
464 					snprintf(szestimate, sizeof(szestimate), "%.0f db", s2n );
465 					REQ(add_to_heard_list, station_calling.c_str(), szestimate);
466 					if (enable_heard_log) {
467 						std::string sheard = zdate();
468 						sheard.append(":").append(ztime());
469 						sheard.append(",").append(station_calling);
470 						sheard.append(",").append(szestimate).append("\n");
471 						heard_log << sheard;
472 						heard_log.flush();
473 					}
474 				}
475 			}
476 			}
477 		}
478 		prev_nibble = curr_nibble;
479 	}
480 
481 	prev_symbol = symbol;
482 }
483 
process_tones()484 void ifkp::process_tones()
485 {
486 	max = 0;
487 	peak = 0;
488 
489 	max = 0;
490 	peak = IFKP_NUMBINS / 2;
491 
492 	int firstbin = frequency * IFKP_SYMLEN / samplerate - IFKP_NUMBINS / 2;
493 
494 	double sigval = 0;
495 
496 	double mins[3];
497 	double min = 3.0e8;
498 	double temp;
499 
500 	mins[0] = mins[1] = mins[2] = 1.0e8;
501 	for (int i = 0; i < IFKP_NUMBINS; ++i) {
502 		val = norm(fft_data[i + firstbin]);
503 // looking for maximum signal
504 		tones[i] = binfilt[i]->run(val);
505 		if (tones[i] > max) {
506 			max = tones[i];
507 			peak = i;
508 		}
509 // looking for minimum signal in a 3 bin sequence
510 		mins[2] = tones[i];
511 		temp = mins[0] + mins[1] + mins[2];
512 		mins[0] = mins[1];
513 		mins[1] = mins[2];
514 		if (temp < min) min = temp;
515 	}
516 
517 	sigval = tones[peak-1] + tones[peak] + tones[peak+1];
518 	if (min == 0) min = 1e-10;
519 
520 	s2n = 10 * log10( snfilt->run(sigval/min)) - 36.0;
521 
522 //scale to -25 to +45 db range
523 // -25 -> 0 linear
524 // 0 - > 45 compressed by 2
525 
526 	if (s2n <= 0) metric = 2 * (25 + s2n);
527 	if (s2n > 0) metric = 50 * ( 1 + s2n / 45);
528 	metric = clamp(metric, 0, 100);
529 
530 	display_metric(metric);
531 
532 	if (peak == prev_peak) {
533 		peak_counter++;
534 	} else {
535 		peak_counter = 0;
536 	}
537 
538 	if ((peak_counter >= peak_hits) &&
539 		(peak != last_peak) &&
540 		(metric >= progStatus.sldrSquelchValue ||
541 		 progStatus.sqlonoff == false)) {
542 		process_symbol(peak);
543 		peak_counter = 0;
544 		last_peak = peak;
545 	}
546 
547 	prev_peak = peak;
548 }
549 
recvpic(double smpl)550 void ifkp::recvpic(double smpl)
551 {
552 	phidiff = 2.0 * M_PI * frequency / samplerate;
553 	phase -= phidiff;
554 	if (phase < 0) phase += 2.0 * M_PI;
555 
556 	cmplx z = smpl * cmplx( cos(phase), sin(phase ) );
557 	picfilter->run( z, currz);
558 	pixel = (samplerate / TWOPI) * pixfilter->run(arg(conj(prevz) * currz));
559 	sync = (samplerate / TWOPI) * syncfilter->run(arg(conj(prevz) * currz));
560 
561 	prevz = currz;
562 
563 	image_counter++;
564 
565 	if (image_counter < 0) return;
566 
567 	if (state == IMAGE_START) {
568 		if (sync < -0.59 * bandwidth) {
569 			state = IMAGE_SYNC;
570 				}
571 		return;
572 	}
573 	if (state == IMAGE_SYNC) {
574 		if (sync > -0.51 * bandwidth) {
575 			state = IMAGE;
576 		}
577 		return;
578 	}
579 
580 	if ((image_counter % IMAGEspp) == 0) {
581 		byte = pixel * 256.0 / bandwidth + 128;
582 		byte = (int)CLAMP( byte, 0.0, 255.0);
583 
584 		if (image_mode == 1) { // bw transmission
585 			pixelnbr = 3 * (col + row * picW);
586 			if (b_ava) {
587 				REQ(ifkp_update_avatar, byte, pixelnbr);
588 				REQ(ifkp_update_avatar, byte, pixelnbr + 1);
589 				REQ(ifkp_update_avatar, byte, pixelnbr + 2);
590 			} else {
591 				REQ(ifkp_updateRxPic, byte, pixelnbr);
592 				REQ(ifkp_updateRxPic, byte, pixelnbr + 1);
593 				REQ(ifkp_updateRxPic, byte, pixelnbr + 2);
594 			}
595 			if (++ col == picW) {
596 				col = 0;
597 				row++;
598 				if (row >= picH) {
599 					rx_reset();
600 					REQ(ifkp_enableshift);
601 				}
602 			}
603 		} else { // color transmission
604 			pixelnbr = rgb + 3 * (col + row * picW);
605 			if (b_ava)
606 				REQ(ifkp_update_avatar, byte, pixelnbr);
607 			else
608 				REQ(ifkp_updateRxPic, byte, pixelnbr);
609 			if (++col == picW) {
610 				col = 0;
611 				if (++rgb == 3) {
612 					rgb = 0;
613 					++row;
614 				}
615 			}
616 			if (row >= picH) {
617 				rx_reset();
618 				REQ(ifkp_enableshift);
619 			}
620 		}
621 	}
622 }
623 
rx_process(const double * buf,int len)624 int ifkp::rx_process(const double *buf, int len)
625 {
626 	double val;
627 	cmplx zin, z;
628 
629 	if (enable_audit_log != progdefaults.ifkp_enable_audit_log ||
630 		enable_heard_log != progdefaults.ifkp_enable_heard_log)
631 		toggle_logs();
632 
633 	if (bkptr < 0) bkptr = 0;
634 	if (bkptr >= IFKP_SHIFT_SIZE) bkptr = 0;
635 
636 	if (progStatus.ifkp_rx_abort) {
637 		state = TEXT;
638 		progStatus.ifkp_rx_abort = false;
639 		REQ(ifkp_clear_rximage);
640 	}
641 
642 	while (len) {
643 		if (state == TEXT) {
644 			rxfilter->Irun(*buf, val);
645 			rx_stream[IFKP_BLOCK_SIZE + bkptr] = val;
646 			bkptr++;
647 
648 			if (bkptr == IFKP_SHIFT_SIZE) {
649 				bkptr = 0;
650 				memmove(rx_stream,								// to
651 						&rx_stream[IFKP_SHIFT_SIZE],			// from
652 						IFKP_BLOCK_SIZE*sizeof(*rx_stream));	// # bytes
653 
654 				for (int n = 0; n < 2*IFKP_FFTSIZE; n++)
655 					fft_data[n] = cmplx(0,0);
656 
657 				for (int i = 0; i < IFKP_BLOCK_SIZE; i++) {
658 					double d = rx_stream[i] * a_blackman[i];
659 					fft_data[i] = cmplx(d,d);
660 				}
661 				fft->ComplexFFT(fft_data);
662 				process_tones();
663 			}
664 		} else
665 			recvpic(*buf);
666 
667 		len--;
668 		buf++;
669 	}
670 	return 0;
671 }
672 
673 //=====================================================================
674 // transmit processing
675 //=====================================================================
676 
transmit(double * buf,int len)677 void ifkp::transmit(double *buf, int len)
678 {
679 //	if (xmtfilt && progdefaults.ifkp_xmtfilter)
680 //		for (int i = 0; i < len; i++) xmtfilt->Irun(buf[i], buf[i]);
681 	ModulateXmtr(buf, len);
682 }
683 
send_tone(int tone)684 void ifkp::send_tone(int tone)
685 {
686 	double phaseincr;
687 	double frequency;
688 
689 	frequency = (basetone + tone * IFKP_SPACING) * samplerate / symlen;
690 	if (test_signal_window && test_signal_window->visible() && btnOffsetOn->value())
691 		frequency += ctrl_freq_offset->value();
692 
693 	phaseincr = 2.0 * M_PI * frequency / samplerate;
694 	prevtone = tone;
695 
696 	int send_symlen = symlen * (
697 			progdefaults.ifkp_baud == 2 ? 0.5 :
698 			progdefaults.ifkp_baud == 0 ? 2.0 : 1.0);
699 
700 	for (int i = 0; i < send_symlen; i++) {
701 		outbuf[i] = cos(txphase);
702 		txphase -= phaseincr;
703 		if (txphase < 0) txphase += TWOPI;
704 	}
705 	transmit(outbuf, send_symlen);
706 }
707 
send_symbol(int sym)708 void ifkp::send_symbol(int sym)
709 {
710 	tone = (prevtone + sym + IFKP_OFFSET) % 33;
711 	send_tone(tone);
712 }
713 
send_idle()714 void ifkp::send_idle()
715 {
716 	send_symbol(0);
717 }
718 
send_char(int ch)719 void ifkp::send_char(int ch)
720 {
721 	if (ch <= 0) return send_idle();
722 
723 	int sym1 = ifkp_varicode[ch][0];
724 	int sym2 = ifkp_varicode[ch][1];
725 
726 	send_symbol(sym1);
727 	if (sym2 > 28)
728 		send_symbol(sym2);
729 	put_echo_char(ch);
730 }
731 
send_avatar()732 void ifkp::send_avatar()
733 {
734 	int W = 59, H = 74;  // grey scale transfer (FAX)
735 	float freq, phaseincr;
736 	float radians = 2.0 * M_PI / samplerate;
737 
738 	freq = frequency - 0.6 * bandwidth;
739 	#define PHASE_CORR  (3 * symlen / 2)
740 	phaseincr = radians * freq;
741 	for (int n = 0; n < PHASE_CORR; n++) {
742 		outbuf[n] = cos(txphase);
743 		txphase -= phaseincr;
744 		if (txphase < 0) txphase += TWOPI;
745 	}
746 	transmit(outbuf, PHASE_CORR);
747 
748 	for (int row = 0; row < H; row++) {
749 		for (int color = 0; color < 3; color++) {
750 			memset(outbuf, 0, IMAGEspp * sizeof(*outbuf));
751 			for (int col = 0; col < W; col++) {
752 				if (stopflag) return;
753 				tx_pixelnbr = col + row * W;
754 				tx_pixel = ifkp_get_avatar_pixel(tx_pixelnbr, color);
755 				freq = frequency + (tx_pixel - 128) * bandwidth / 256.0;
756 				phaseincr = radians * freq;
757 				for (int n = 0; n < IMAGEspp; n++) {
758 					outbuf[n] = cos(txphase);
759 					txphase -= phaseincr;
760 					if (txphase < 0) txphase += TWOPI;
761 				}
762 				transmit(outbuf, IMAGEspp);
763 			}
764 			Fl::awake();
765 		}
766 	}
767 }
768 
769 static bool send_color = true;
770 
send_image()771 void ifkp::send_image()
772 {
773 	int W = 640, H = 480;  // grey scale transfer (FAX)
774 	float freq, phaseincr;
775 	float radians = 2.0 * M_PI / samplerate;
776 
777 	if (!ifkppicTxWin || !ifkppicTxWin->visible()) {
778 		return;
779 	}
780 
781 	switch (selifkppicSize->value()) {
782 		case 0 : W = 59; H = 74; break;
783 		case 1 : W = 120; H = 150; break;
784 		case 2 : W = 240; H = 300; break;
785 		case 3 : W = 160; H = 120; break;
786 		case 4 : W = 320; H = 240; break;
787 		case 5 : W = 640; H = 480; break;
788 	}
789 
790 	REQ(ifkp_clear_tximage);
791 
792 	stop_deadman();
793 
794 	for (size_t n = 0; n < imageheader.length(); n++)
795 		send_char(imageheader[n]);
796 	send_char(0); // needed to flush the header at the Rx decoder
797 
798 	freq = frequency - 0.6 * bandwidth; // black-black
799 	phaseincr = radians * freq;
800 
801 	for (int j = 0; j < 7; j++) {
802 		for (int i = 0; i < symlen/2; i++) {
803 			outbuf[i] = cos(txphase);
804 			txphase -= phaseincr;
805 			if (txphase < 0) txphase += TWOPI;
806 		}
807 		transmit(outbuf, symlen/2);
808 	}
809 
810 	if (send_color == false) {  // grey scale image
811 		for (int row = 0; row < H; row++) {
812 			memset(outbuf, 0, IMAGEspp * sizeof(*outbuf));
813 			for (int col = 0; col < W; col++) {
814 				if (stopflag)
815 					goto end_image;
816 				tx_pixelnbr = col + row * W;
817 				tx_pixel =	0.3 * ifkppic_TxGetPixel(tx_pixelnbr, 0) +   // red
818 							0.6 * ifkppic_TxGetPixel(tx_pixelnbr, 1) +   // green
819 							0.1 * ifkppic_TxGetPixel(tx_pixelnbr, 2);    // blue
820 				REQ(ifkp_updateTxPic, tx_pixel, tx_pixelnbr*3 + 0);
821 				REQ(ifkp_updateTxPic, tx_pixel, tx_pixelnbr*3 + 1);
822 				REQ(ifkp_updateTxPic, tx_pixel, tx_pixelnbr*3 + 2);
823 				freq = frequency + (tx_pixel - 128) * bandwidth / 256.0;
824 				phaseincr = radians * freq;
825 				for (int n = 0; n < IMAGEspp; n++) {
826 					outbuf[n] = cos(txphase);
827 					txphase -= phaseincr;
828 					if (txphase < 0) txphase += TWOPI;
829 				}
830 				transmit(outbuf, IMAGEspp);
831 				Fl::awake();
832 			}
833 		}
834 	} else {
835 		for (int row = 0; row < H; row++) {
836 			for (int color = 0; color < 3; color++) {
837 				memset(outbuf, 0, IMAGEspp * sizeof(*outbuf));
838 				for (int col = 0; col < W; col++) {
839 					if (stopflag)
840 						goto end_image;
841 					tx_pixelnbr = col + row * W;
842 					tx_pixel = ifkppic_TxGetPixel(tx_pixelnbr, color);
843 					REQ(ifkp_updateTxPic, tx_pixel, tx_pixelnbr*3 + color);
844 					freq = frequency + (tx_pixel - 128) * bandwidth / 256.0;
845 					phaseincr = radians * freq;
846 					for (int n = 0; n < IMAGEspp; n++) {
847 						outbuf[n] = cos(txphase);
848 						txphase -= phaseincr;
849 						if (txphase < 0) txphase += TWOPI;
850 					}
851 					transmit(outbuf, IMAGEspp);
852 				}
853 				Fl::awake();
854 				if (stopflag) goto end_image;
855 			}
856 		}
857 	}
858 end_image:
859 	start_deadman();
860 }
861 
862 std::string img_str;
863 
ifkp_send_image(std::string image_str,bool gray)864 void ifkp::ifkp_send_image(std::string image_str, bool gray) {
865 	send_color = !gray;
866 	imageheader = image_str;
867 	TX_IMAGE = true;
868 	start_tx();
869 }
870 
ifkp_send_avatar()871 void ifkp::ifkp_send_avatar() {
872 	img_str = "\npic%A";
873 	TX_AVATAR = true;
874 	start_tx();
875 }
876 
m_ifkp_send_avatar()877 void ifkp::m_ifkp_send_avatar()
878 {
879 	std::string mycall = progdefaults.myCall;
880 	for (size_t n = 0; n < mycall.length(); n++)
881 		mycall[n] = tolower(mycall[n]);
882 	std::string fname = AvatarDir;
883 	fname.append(mycall).append(".png");
884 
885 	my_avatar_img = Fl_Shared_Image::get(fname.c_str(), 59, 74);
886 	if (!my_avatar_img) return;
887 	unsigned char *img_data = (unsigned char *)my_avatar_img->data()[0];
888 	memset(avatar, 0, sizeof(avatar));
889 	int D = my_avatar_img->d();
890 
891 	if (D == 3)
892 		memcpy(avatar, img_data, 59*74*3);
893 	else if (D == 4) {
894 		int i, j, k;
895 		for (i = 0; i < 59*74; i++) {
896 			j = i*3; k = i*4;
897 			avatar[j] = img_data[k];
898 			avatar[j+1] = img_data[k+1];
899 			avatar[j+2] = img_data[k+2];
900 		}
901 	} else if (D == 1) {
902 		int i, j;
903 		for (i = 0; i < 59*74; i++) {
904 			j = i * 3;
905 			avatar[j] = avatar[j+1] = avatar[j+2] = img_data[i];
906 		}
907 	} else
908 		return;
909 
910 	ifkp_send_avatar();
911 }
912 
tx_process()913 int ifkp::tx_process()
914 {
915 	modem::tx_process();
916 
917 	if (send_bot) {
918 		send_bot = false;
919 		send_char(0);
920 		send_char(0);
921 	}
922 
923 	int c = get_tx_char();
924 
925 //	if (c == GET_TX_CHAR_ETX || enable_image) {
926 	if (TX_IMAGE || TX_AVATAR) {
927 		if (img_str.length()) {
928 			for (size_t n = 0; n < img_str.length(); n++)
929 				send_char(img_str[n]);
930 		}
931 		img_str.clear();
932 
933 		if (TX_IMAGE) {
934 			send_image();
935 			ifkppicTxWin->hide();
936 		}
937 		if (TX_AVATAR)
938 			send_avatar();
939 
940 		send_char(0);
941 
942 		TX_IMAGE = false;
943 		TX_AVATAR = false;
944 		ifkppicTxWin->hide();
945 
946 		return 0;
947 
948 	}
949 	if ( stopflag || c == GET_TX_CHAR_ETX) { // aborts transmission
950 		send_char(0);
951 //		TX_IMAGE = false;
952 //		TX_AVATAR = false;
953 		stopflag = false;
954 		return -1;
955 	}
956 	send_char(c);
957 	return 0;
958 }
959 
960