1 // ----------------------------------------------------------------------------
2 // throb.cxx  --  throb modem
3 //
4 // Copyright (C) 2006-2010
5 //		Dave Freese, W1HKJ
6 // ThrobX additions by Joe Veldhuis, KD8ATU
7 //
8 // This file is part of fldigi.  Adapted from code contained in gmfsk source code
9 // distribution.
10 //  gmfsk Copyright (C) 2001, 2002, 2003
11 //  Tomi Manninen (oh2bns@sral.fi)
12 //
13 // This file is part of fldigi.
14 //
15 // Fldigi is free software: you can redistribute it and/or modify
16 // it under the terms of the GNU General Public License as published by
17 // the Free Software Foundation, either version 3 of the License, or
18 // (at your option) any later version.
19 //
20 // Fldigi is distributed in the hope that it will be useful,
21 // but WITHOUT ANY WARRANTY; without even the implied warranty of
22 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23 // GNU General Public License for more details.
24 //
25 // You should have received a copy of the GNU General Public License
26 // along with fldigi.  If not, see <http://www.gnu.org/licenses/>.
27 // ----------------------------------------------------------------------------
28 
29 #include <config.h>
30 
31 #include <stdlib.h>
32 #include <iostream>
33 
34 #include "throb.h"
35 #include "ascii.h"
36 #include "configuration.h"
37 #include "fl_digi.h"
38 #include "status.h"
39 
40 #define MAX_TONES	15
41 
42 #undef  CLAMP
43 #define CLAMP(x,low,high)       (((x)>(high))?(high):(((x)<(low))?(low):(x)))
44 
45 char throbmsg[80];
46 
tx_init()47 void  throb::tx_init()
48 {
49 	preamble = 4;
50 	reset_syms();
51 	videoText();
52 }
53 
rx_init()54 void  throb::rx_init()
55 {
56 	rxcntr = rxsymlen;
57 	waitsync = 1;
58 	deccntr = 0;
59 	symptr = 0;
60 	shift = 0;
61 	lastchar = '\0';
62 	reset_syms();
63 	put_MODEstatus(mode);
64 }
65 
init()66 void throb::init()
67 {
68 	modem::init();
69 	rx_init();
70 	set_scope_mode(Digiscope::SCOPE);
71 
72 	if (progdefaults.StartAtSweetSpot)
73 		set_freq(progdefaults.PSKsweetspot);
74 	else if (progStatus.carrier != 0) {
75 		set_freq(progStatus.carrier);
76 #if !BENCHMARK_MODE
77 		progStatus.carrier = 0;
78 #endif
79 	} else
80 		set_freq(wf->Carrier());
81 }
82 
~throb()83 throb::~throb()
84 {
85 	if (hilbert) delete hilbert;
86 	if (syncfilt) delete syncfilt;
87 	if (fftfilter) delete fftfilter;
88 	if (snfilter) delete snfilter;
89 	if (scope_data) delete [] scope_data;
90 	if (txpulse) delete [] txpulse;
91 	if (outbuf) delete[] outbuf;
92 	for (int i = 0; i < num_tones; i++)
93 		if (rxtone[i]) delete [] rxtone[i];
94 }
95 
flip_syms()96 void throb::flip_syms() //call this whenever a space or idle is sent or received
97 {
98 	switch(mode) {
99 		case MODE_THROBX1:
100 		case MODE_THROBX2:
101 		case MODE_THROBX4:
102 			if (idlesym == 0) {
103 				idlesym = 1;
104 				spacesym = 0;
105 			} else {
106 				idlesym = 0;
107 				spacesym = 1;
108 			}
109 			break;
110 		default:
111 			//if we're not running a ThrobX mode, do nothing
112 			break;
113 	}
114 }
115 
reset_syms()116 void throb::reset_syms() //call when switching from TX to RX or vice versa
117 {
118         switch(mode) {
119         case MODE_THROBX1:
120         case MODE_THROBX2:
121         case MODE_THROBX4:
122         	idlesym = 0;
123 		spacesym = 1;
124 		break;
125 	default: //paranoia
126 		idlesym = 0;
127 		spacesym = 44;
128 		break;
129 	}
130 }
131 
throb(trx_mode throb_mode)132 throb::throb(trx_mode throb_mode) : modem()
133 {
134 	cap |= CAP_AFC | CAP_REV;
135 
136 	double bw;
137 	double *fp = 0;
138 
139 	mode = throb_mode;
140 
141 	switch (mode) {
142 	case MODE_THROB1:
143 		symlen = SYMLEN_1;
144 		txpulse = mk_semi_pulse(symlen);
145 		fp = mk_semi_pulse(symlen / DOWN_SAMPLE);
146 		num_tones = 9;
147 		num_chars = 45;
148 		idlesym = 0;
149 		spacesym = 44;
150 		for (int i = 0; i < num_tones; i++)
151 			freqs[i] = ThrobToneFreqsNar[i];
152 		bw = 36.0 / THROB_SAMPLE_RATE;
153 		break;
154 
155 	case MODE_THROB2:
156 		symlen = SYMLEN_2;
157 		txpulse = mk_semi_pulse(symlen);
158 		fp = mk_semi_pulse(symlen / DOWN_SAMPLE);
159 		num_tones = 9;
160 		num_chars = 45;
161 		idlesym = 0;
162 		spacesym = 44;
163 		for (int i = 0; i < num_tones; i++)
164 			freqs[i] = ThrobToneFreqsNar[i];
165 		bw = 36.0 / THROB_SAMPLE_RATE;
166 		break;
167 
168 	case MODE_THROB4:
169 	default:
170 		symlen = SYMLEN_4;
171 		txpulse = mk_full_pulse(symlen);
172 		fp = mk_full_pulse(symlen / DOWN_SAMPLE);
173 		num_tones = 9;
174 		num_chars = 45;
175 		idlesym = 0;
176 		spacesym = 44;
177 		for (int i = 0; i < num_tones; i++)
178 			freqs[i] = ThrobToneFreqsWid[i];
179 		bw = 72.0 / THROB_SAMPLE_RATE;
180 		break;
181 
182         case MODE_THROBX1:
183                 symlen = SYMLEN_1;
184                 txpulse = mk_semi_pulse(symlen);
185                 fp = mk_semi_pulse(symlen / DOWN_SAMPLE);
186                 num_tones = 11;
187                 num_chars = 55;
188 				idlesym = 0;
189 				spacesym = 1;
190                 for (int i = 0; i < num_tones; i++)
191                         freqs[i] = ThrobXToneFreqsNar[i];
192                 bw = 47.0 / THROB_SAMPLE_RATE;
193 		break;
194 
195         case MODE_THROBX2:
196                 symlen = SYMLEN_2;
197                 txpulse = mk_semi_pulse(symlen);
198                 fp = mk_semi_pulse(symlen / DOWN_SAMPLE);
199                 num_tones = 11;
200                 num_chars = 55;
201 				idlesym = 0;
202 				spacesym = 1;
203                 for (int i = 0; i < num_tones; i++)
204 					freqs[i] = ThrobXToneFreqsNar[i];
205                 bw = 47.0 / THROB_SAMPLE_RATE;
206                 break;
207 
208         case MODE_THROBX4: //NONSTANDARD
209                 symlen = SYMLEN_4;
210                 txpulse = mk_full_pulse(symlen);
211                 fp = mk_full_pulse(symlen / DOWN_SAMPLE);
212                 num_tones = 11;
213                 num_chars = 55;
214 				idlesym = 0;
215 				spacesym = 1;
216                 for (int i = 0; i < num_tones; i++)
217                         freqs[i] = ThrobXToneFreqsWid[i];
218                 bw = 94.0 / THROB_SAMPLE_RATE;
219                 break;
220 	}
221 
222 	outbuf = new double[symlen];
223 
224 	rxsymlen = symlen / DOWN_SAMPLE;
225 
226 	hilbert		= new C_FIR_filter();
227 	hilbert->init_hilbert(37, 1);
228 
229 	fftfilter = new fftfilt(0, bw, FilterFFTLen);
230 
231 	syncfilt = new C_FIR_filter();
232 	syncfilt->init(symlen / DOWN_SAMPLE, 1, fp, NULL);
233 	delete [] fp;
234 
235 	snfilter = new Cmovavg(16);
236 
237 	for (int i = 0; i < num_tones; i++)
238 		rxtone[i] = mk_rxtone(freqs[i], txpulse, symlen);
239 
240 	reverse = 0;
241 	samplerate = THROB_SAMPLE_RATE;
242 	fragmentsize = symlen;
243 	bandwidth = freqs[num_tones - 1] - freqs[0];
244 	syncpos = 0.5;
245 
246 	scope_data	= new double [SCOPE_DATA_LEN];
247 
248 	phaseacc = 0.0;
249 	metric = 0.0;
250 	symptr = 0;
251 
252 	for (int i = 0; i < MAX_RX_SYMLEN; i++)
253 		syncbuf[i] = dispbuf[i] = 0.0;
254 
255 //	init();
256 }
257 
258 //=====================================================================
259 // receive processing
260 //=====================================================================
261 
262 // Make a 32 times down sampled cmplx prototype tone for rx.
263 
mk_rxtone(double freq,double * pulse,int len)264 cmplx *throb::mk_rxtone(double freq, double *pulse, int len)
265 {
266 	cmplx *tone;
267 	double x;
268 	int i;
269 
270 	tone = new cmplx [len / DOWN_SAMPLE];
271 
272 	for (i = 0; i < len; i += DOWN_SAMPLE) {
273 		x = -2.0 * M_PI * freq * i / THROB_SAMPLE_RATE;
274 		tone[i / DOWN_SAMPLE] =
275 			cmplx ( pulse[i] * cos(x), pulse[i] * sin(x) );
276 	}
277 
278 	return tone;
279 }
280 
mixer(cmplx in)281 cmplx throb::mixer(cmplx in)
282 {
283 	double f;
284 	cmplx z (cos(phaseacc), sin(phaseacc));
285 
286 	z = z * in;
287 
288 	f = frequency;
289 
290 	phaseacc -= 2.0 * M_PI * f / THROB_SAMPLE_RATE;
291 
292 	if (phaseacc < 0) phaseacc += TWOPI;
293 
294 	return z;
295 }
296 
findtones(cmplx * word,int & tone1,int & tone2)297 int throb::findtones(cmplx *word, int &tone1, int &tone2)
298 {
299 	double max1, max2;
300 	int maxtone, i;
301 
302 	max1 = 0;
303 	tone1 = 0;
304 	for (i = 0; i < num_tones; i++) {
305 		if ( abs(word[i]) > max1 ) {
306 			max1 = abs(word[i]);
307 			tone1 = i;
308 		}
309 	}
310 
311 	maxtone = tone1;
312 
313 	max2 = 0;
314 	tone2 = 0;
315 	for (i = 0; i < num_tones; i++) {
316 		if (i == tone1)
317 			continue;
318 		if ( abs(word[i]) > max2) {
319 			max2 = abs(word[i]);
320 			tone2 = i;
321 		}
322 	}
323 
324 //handle single-tone symbols (Throb only)
325 	if (mode == MODE_THROB1 ||
326 		mode == MODE_THROB2 ||
327 		mode == MODE_THROB4)
328 		if (max1 > max2 * 2)
329 			tone2 = tone1;
330 
331 	if (tone1 > tone2) {
332 		i = tone1;
333 		tone1 = tone2;
334 		tone2 = i;
335 	}
336 
337 	signal = noise = 0.0;
338 	for (i = 0; i < num_tones; i++) {
339 		if ( i == tone1 || i == tone2)
340 			signal += abs(word[i]) / 2.0;
341 		else
342 			noise += abs(word[i]) / (num_tones - 2.0);
343 	}
344 
345 	metric = snfilter->run( signal / (noise + 1e-6));
346 
347 	s2n = CLAMP( 10.0*log10( metric ) - 3.0, 0.0, 100.0);
348 
349 	return maxtone;
350 }
351 
show_char(int c)352 void throb::show_char(int c) {
353 	if (metric > progStatus.sldrSquelchValue || progStatus.sqlonoff == false)
354 		put_rx_char(progdefaults.rx_lowercase ? tolower(c) : c);
355 }
356 
decodechar(int tone1,int tone2)357 void throb::decodechar(int tone1, int tone2)
358 {
359 	int i;
360 
361 	switch(mode) {
362 	case MODE_THROB1:
363 	case MODE_THROB2:
364 	case MODE_THROB4:
365 		if (shift == true) {
366 			if (tone1 == 0 && tone2 == 8)
367 				show_char('?');
368 
369 			if (tone1 == 1 && tone2 == 7)
370 				show_char('@');
371 
372 			if (tone1 == 2 && tone2 == 6)
373 				show_char('=');
374 
375 			if (tone1 == 4 && tone2 == 4)
376 				show_char('\n');
377 
378 			shift = false;
379 			return;
380 		}
381 
382 		if (tone1 == 3 && tone2 == 5) {
383 			shift = true;
384 			return;
385 		}
386 
387 		for (i = 0; i < num_chars; i++) {
388 			if (ThrobTonePairs[i][0] == tone1 + 1 &&
389 				ThrobTonePairs[i][1] == tone2 + 1) {
390 				show_char(ThrobCharSet[i]);
391 				break;
392 			}
393 		}
394 		break;
395 //ThrobX mode. No shifted case, but idle and space symbols alternate
396 	default:
397 		for (i = 0; i < num_chars; i++) {
398 			if (ThrobXTonePairs[i][0] == tone1 + 1 && ThrobXTonePairs[i][1] == tone2 + 1) {
399 				if (i == spacesym || i == idlesym) {
400 					if (lastchar != '\0' && lastchar != ' ') {
401 						show_char(ThrobXCharSet[1]);
402 						lastchar = ' ';
403 					}
404 					else {
405 						lastchar = '\0';
406 					}
407 					flip_syms();
408 				} else {
409 					show_char(ThrobXCharSet[i]);
410 					lastchar = ThrobXCharSet[i];
411 				}
412 			}
413 		}
414 	break;
415 	}
416 	return;
417 }
418 
rx(cmplx in)419 void throb::rx(cmplx in)
420 {
421 	cmplx rxword[MAX_TONES];
422 	int i, tone1, tone2, maxtone;
423 
424 	symbol[symptr] = in;
425 
426 	if (rxcntr > 0.0)
427 		return;
428 
429 // correlate against all tones
430 	for (i = 0; i < num_tones; i++)
431 		rxword[i] = cmac(rxtone[i], symbol, symptr + 1, rxsymlen);
432 
433 // find the strongest tones
434 	maxtone = findtones(rxword, tone1, tone2);
435 
436 // decode
437 	if (reverse)
438 		decodechar (num_tones - 1 - tone2, num_tones - 1 - tone1);
439 	else
440 		decodechar (tone1, tone2);
441 
442 	if (progStatus.afconoff == true && (metric >= progStatus.sldrSquelchValue || progStatus.sqlonoff == false)) {
443 		cmplx z1, z2;
444 		double f;
445 
446 		z1 = rxword[maxtone];
447 		z2 = cmac(rxtone[maxtone], symbol, symptr + 2, rxsymlen);
448 
449 		f = arg( conj(z1) * z2 ) / (2 * DOWN_SAMPLE * M_PI / THROB_SAMPLE_RATE);
450 
451 		f -= freqs[maxtone];
452 
453 		set_freq(frequency + f / (num_tones - 1));
454 	}
455 
456 	/* done with this symbol, start over */
457 	rxcntr = rxsymlen;
458 	waitsync = 1;
459 
460 	snprintf(throbmsg, sizeof(throbmsg), "S/N: %3d dB", (int)(floor(s2n)));
461 	put_Status1(throbmsg);
462 	display_metric(metric);
463 
464 }
465 
sync(cmplx in)466 void throb::sync(cmplx in)
467 {
468 	double f, maxval = 0;
469 	double mag;
470 	int i, maxpos = 0;
471 
472 	/* "rectify", filter and store input */
473 	mag = abs(in);
474 	syncfilt->Irun( mag, f);
475 	syncbuf[symptr] = f;
476 
477 	/* check counter if we are waiting for sync */
478 	if (waitsync == 0 || rxcntr > (rxsymlen / 2.0))
479 		return;
480 
481 	for (i = 0; i < rxsymlen; i++) {
482 		f = syncbuf[(i + symptr + 1) % rxsymlen];
483 		dispbuf[i] = f;
484 	}
485 
486 	for (i = 0; i < rxsymlen; i++) {
487 		if (dispbuf[i] > maxval) {
488 			maxpos = i;
489 			maxval = dispbuf[i];
490 		}
491 	}
492 
493 	/* correct sync */
494 	rxcntr += (maxpos - rxsymlen / 2) / (num_tones - 1);
495 	waitsync = 0;
496 	if (metric >= progStatus.sldrSquelchValue || progStatus.sqlonoff == false)
497 		set_scope(dispbuf, rxsymlen);
498 	else {
499 		dispbuf[0] = 0.0;
500 		set_scope(dispbuf, 1);
501 	}
502 	dispbuf.next(); // change buffers
503 }
504 
rx_process(const double * buf,int len)505 int throb::rx_process(const double *buf, int len)
506 {
507 	cmplx z, *zp;
508 	int i, n;
509 
510 	while (len-- > 0) {
511 		z = cmplx( *buf, *buf );
512 		buf++;
513 
514 		hilbert->run(z, z);
515 		z = mixer(z);
516 		n = fftfilter->run(z, &zp);
517 
518 		/* DOWN_SAMPLE by 32 and push to the receiver */
519 		for (i = 0; i < n; i++) {
520 			if (++deccntr >= DOWN_SAMPLE) {
521 				rxcntr -= 1.0;
522 
523 				/* do symbol sync */
524 				sync(zp[i]);
525 
526 				/* decode */
527 				rx(zp[i]);
528 
529 				symptr = (symptr + 1) % rxsymlen;
530 				deccntr = 0;
531 			}
532 		}
533 	}
534 
535 	return 0;
536 }
537 
538 //=====================================================================
539 // transmit processing
540 //=====================================================================
541 
mk_semi_pulse(int len)542 double *throb::mk_semi_pulse(int len)
543 {
544 	double *pulse, x;
545 	int i, j;
546 
547 	pulse = new double [len];
548 
549 	for (i = 0; i < len; i++) {
550 		if (i < len / 5) {
551 			x = M_PI * i / (len / 5.0);
552 			pulse[i] = 0.5 * (1 - cos(x));
553 		}
554 
555 		if (i >= len / 5 && i < len * 4 / 5)
556 			pulse[i] = 1.0;
557 
558 		if (i >= len * 4 / 5) {
559 			j = i - len * 4 / 5;
560 			x = M_PI * j / (len / 5.0);
561 			pulse[i] = 0.5 * (1 + cos(x));
562 		}
563 	}
564 
565 	return pulse;
566 }
567 
mk_full_pulse(int len)568 double *throb::mk_full_pulse(int len)
569 {
570 	double *pulse;
571 	int i;
572 
573 	pulse = new double [len];
574 
575 	for (i = 0; i < len; i++)
576 		pulse[i] = 0.5 * (1 - cos(2 * M_PI * i / len));
577 
578 	return pulse;
579 }
580 
581 
send(int symbol)582 void throb::send(int symbol)
583 {
584 	int tone1, tone2;
585 	double w1, w2;
586 	int i;
587 
588 	if (symbol < 0 || symbol >= num_chars)
589 		return;
590 
591 	switch(mode) {
592 	case MODE_THROB1:
593 	case MODE_THROB2:
594 	case MODE_THROB4:
595 		tone1 = ThrobTonePairs[symbol][0] - 1;
596 		tone2 = ThrobTonePairs[symbol][1] - 1;
597 		break;
598 	default:
599 		tone1 = ThrobXTonePairs[symbol][0] -1;
600 		tone2 = ThrobXTonePairs[symbol][1] -1;
601 		break;
602 	}
603 
604 	if (reverse) {
605 		tone1 = (num_tones - 1) - tone1;
606 		tone2 = (num_tones - 1) - tone2;
607 	}
608 
609 	w1 = 2.0 * M_PI * (get_txfreq_woffset() + freqs[tone1]) / THROB_SAMPLE_RATE;
610 	w2 = 2.0 * M_PI * (get_txfreq_woffset() + freqs[tone2]) / THROB_SAMPLE_RATE;
611 
612 	for (i = 0; i < symlen; i++)
613 		outbuf[i] = txpulse[i] *
614 				 (sin(w1 * i) + sin(w2 * i)) / 2.0;
615 
616 	ModulateXmtr(outbuf, symlen);
617 }
618 
tx_process()619 int throb::tx_process()
620 {
621 	modem::tx_process();
622 
623 	int i, c, sym;
624 
625 	if (preamble > 0) {
626 		send(idlesym);	/* send idle throbs */
627 		flip_syms();
628 		preamble--;
629 		return 0;
630 	}
631 
632 	c = get_tx_char();
633 
634 // end of transmission
635 	if (c == GET_TX_CHAR_ETX || stopflag) {
636 		send(idlesym);
637 //		reset_syms(); //prepare RX. idle/space syms always start as 0 and 1, respectively.
638 		return -1;
639 	}
640 
641 // TX buffer empty
642 	if (c == GET_TX_CHAR_NODATA) {
643 		send(idlesym);	/* send idle throbs */
644 		flip_syms();
645 		return 0;
646 	}
647 
648 	switch(mode) {
649 	case MODE_THROB1:
650 	case MODE_THROB2:
651 	case MODE_THROB4:
652 	/* handle the special cases first, if we're doing regular Throb */
653 	switch (c) {
654 	case '?':
655 		send(5);	/* shift */
656 		send(20);
657 		put_echo_char(c);
658 		return 0;
659 
660 	case '@':
661 		send(5);	/* shift */
662 		send(13);
663 		put_echo_char(c);
664 		return 0;
665 
666 	case '-':
667 		send(5);	/* shift */
668 		send(9);
669 		put_echo_char(c);
670 		return 0;
671 
672 	case '\r':
673 		return 0;
674 	case '\n':
675 		send(5);	/* shift */
676 		send(0);
677 		put_echo_char(c);
678 		return 0;
679 
680 	default:
681 		break;
682 	}
683 	break;
684 	default:
685 	//If we're doing ThrobX, no need to handle shifts
686 	break;
687 	}
688 
689 	/* map lower case character to upper case */
690 	if (islower(c))
691 		c = toupper(c);
692 
693 	/* see if the character can be found in our character set */
694 	switch(mode) {
695 	case MODE_THROB1:
696 	case MODE_THROB2:
697 	case MODE_THROB4:
698 		for (sym = -1, i = 0; i < num_chars; i++)
699 			if (c == ThrobCharSet[i])
700 				sym = i;
701 		break;
702 	default:
703 		for (sym = -1, i = 0; i < num_chars; i++)
704                         if (c == ThrobXCharSet[i])
705                                 sym = i;
706                 break;
707 	}
708 
709 // send a space for unknown chars
710 	if (sym == -1) c = ' ';
711 // handle spaces for throbx
712 	if (c == ' ') {
713 		sym = spacesym;
714 		flip_syms();
715 	}
716 
717 	send(sym);
718 	put_echo_char(progdefaults.rx_lowercase ? tolower(c) : c);
719 
720 	return 0;
721 }
722 
723 
724 //=====================================================================
725 // throb static declarations
726 //=====================================================================
727 
728 
729 int throb::ThrobTonePairs[45][2] = {
730 	{5, 5},			/* idle... no print */
731 	{4, 5},			/* A */
732 	{1, 2},			/* B */
733 	{1, 3},			/* C */
734 	{1, 4},			/* D */
735 	{4, 6},			/* SHIFT (was E) */
736 	{1, 5},			/* F */
737 	{1, 6},			/* G */
738 	{1, 7},			/* H */
739 	{3, 7},			/* I */
740 	{1, 8},			/* J */
741 	{2, 3},			/* K */
742 	{2, 4},			/* L */
743 	{2, 8},			/* M */
744 	{2, 5},			/* N */
745 	{5, 6},			/* O */
746 	{2, 6},			/* P */
747 	{2, 9},			/* Q */
748 	{3, 4},			/* R */
749 	{3, 5},			/* S */
750 	{1, 9},			/* T */
751 	{3, 6},			/* U */
752 	{8, 9},			/* V */
753 	{3, 8},			/* W */
754 	{3, 3},			/* X */
755 	{2, 2},			/* Y */
756 	{1, 1},			/* Z */
757 	{3, 9},			/* 1 */
758 	{4, 7},			/* 2 */
759 	{4, 8},			/* 3 */
760 	{4, 9},			/* 4 */
761 	{5, 7},			/* 5 */
762 	{5, 8},			/* 6 */
763 	{5, 9},			/* 7 */
764 	{6, 7},			/* 8 */
765 	{6, 8},			/* 9 */
766 	{6, 9},			/* 0 */
767 	{7, 8},			/* , */
768 	{7, 9},			/* . */
769 	{8, 8},			/* ' */
770 	{7, 7},			/* / */
771 	{6, 6},			/* ) */
772 	{4, 4},			/* ( */
773 	{9, 9},			/* E */
774 	{2, 7}			/* space */
775 };
776 
777 int throb::ThrobXTonePairs[55][2] = {
778         {6, 11},                /* idle (initially) */
779         {1, 6},                 /* space (initially) */
780         {2, 6},                 /* A */
781         {2, 5},                 /* B */
782         {2, 7},                 /* C */
783         {2, 8},                 /* D */
784         {5, 6},                 /* E */
785         {2, 9},                 /* F */
786         {2, 10},                /* G */
787         {4, 8},                 /* H */
788         {4, 6},                 /* I */
789         {2, 11},                /* J */
790         {3, 4},                 /* K */
791         {3, 5},                 /* L */
792         {3, 6},                 /* M */
793         {6, 9},                 /* N */
794         {6, 10},                /* O */
795         {3, 7},                 /* P */
796         {3, 8},                 /* Q */
797         {3, 9},                 /* R */
798         {6, 8},                 /* S */
799         {6, 7},                 /* T */
800         {3, 10},                /* U */
801         {3, 11},                /* V */
802         {4, 5},                 /* W */
803         {4, 7},                 /* X */
804         {4, 9},                 /* Y */
805         {4, 10},                /* Z */
806         {1, 2},                 /* 1 */
807         {1, 3},                 /* 2 */
808         {1, 4},                 /* 3 */
809         {1, 5},                 /* 4 */
810         {1, 7},                 /* 5 */
811         {1, 8},                 /* 6 */
812         {1, 9},                 /* 7 */
813         {1, 10},                /* 8 */
814         {2, 3},                 /* 9 */
815         {2, 4},                 /* 0 */
816         {4, 11},                /* , */
817         {5, 7},                 /* . */
818         {5, 8},                 /* ' */
819         {5, 9},                 /* / */
820         {5, 10},                /* ) */
821         {5, 11},                /* ( */
822         {7, 8},                 /* # */
823         {7, 9},                 /* " */
824         {7, 10},                /* + */
825         {7, 11},                /* - */
826         {8, 9},                 /* ; */
827         {8, 10},                /* : */
828         {8, 11},                /* ? */
829         {9, 10},                /* ! */
830         {9, 11},                /* @ */
831         {10, 11},               /* = */
832 	{1, 11}			/* cr */ //FIXME: !!COMPLETELY NONSTANDARD!!
833 };
834 
835 unsigned char throb::ThrobCharSet[45] = {
836 	'\0',			/* idle */
837 	'A',
838 	'B',
839 	'C',
840 	'D',
841 	'\0',			/* shift */
842 	'F',
843 	'G',
844 	'H',
845 	'I',
846 	'J',
847 	'K',
848 	'L',
849 	'M',
850 	'N',
851 	'O',
852 	'P',
853 	'Q',
854 	'R',
855 	'S',
856 	'T',
857 	'U',
858 	'V',
859 	'W',
860 	'X',
861 	'Y',
862 	'Z',
863 	'1',
864 	'2',
865 	'3',
866 	'4',
867 	'5',
868 	'6',
869 	'7',
870 	'8',
871 	'9',
872 	'0',
873 	',',
874 	'.',
875 	'\'',
876 	'/',
877 	')',
878 	'(',
879 	'E',
880 	' '
881 };
882 
883 unsigned char throb::ThrobXCharSet[55] = {
884         '\0',                   /* idle (initially) */
885         ' ',                    /* space (initially) */
886         'A',
887         'B',
888         'C',
889         'D',
890         'E',
891         'F',
892         'G',
893         'H',
894         'I',
895         'J',
896         'K',
897         'L',
898         'M',
899         'N',
900         'O',
901         'P',
902         'Q',
903         'R',
904         'S',
905         'T',
906         'U',
907         'V',
908         'W',
909         'X',
910         'Y',
911         'Z',
912         '1',
913         '2',
914         '3',
915         '4',
916         '5',
917         '6',
918         '7',
919         '8',
920         '9',
921         '0',
922         ',',
923         '.',
924         '\'',
925         '/',
926         ')',
927         '(',
928         '#',
929         '"',
930         '+',
931         '-',
932         ';',
933         ':',
934         '?',
935         '!',
936         '@',
937         '=',
938 	'\n'
939 };
940 
941 double throb::ThrobToneFreqsNar[9] = {-32, -24, -16,  -8,  0,  8, 16, 24, 32};
942 double throb::ThrobToneFreqsWid[9] = {-64, -48, -32, -16,  0, 16, 32, 48, 64};
943 double throb::ThrobXToneFreqsNar[11] = {-39.0625, -31.25, -23.4375, -15.625, -7.8125, 0, 7.8125, 15.625, 23.4375, 31.25, 39.0625};
944 double throb::ThrobXToneFreqsWid[11] = {-78.125, -62.5, -46.875, -31.25, -15.625, 0, 15.625, 31.25, 46.875, 62.5, 78.125};
945