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