1 // ----------------------------------------------------------------------------
2 // rtty.cxx  --  RTTY modem
3 //
4 // Copyright (C) 2006-2010
5 //		Dave Freese, W1HKJ
6 //
7 // This file is part of fldigi.  Adapted from code contained in gmfsk source code
8 // distribution.
9 //
10 // Fldigi is free software: you can redistribute it and/or modify
11 // it under the terms of the GNU General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // Fldigi is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with fldigi.  If not, see <http://www.gnu.org/licenses/>.
22 // ----------------------------------------------------------------------------
23 
24 #include <config.h>
25 #include <iostream>
26 using namespace std;
27 
28 //#include "rtty.h"
29 #include "view_rtty.h"
30 #include "fl_digi.h"
31 #include "digiscope.h"
32 #include "misc.h"
33 #include "waterfall.h"
34 #include "confdialog.h"
35 #include "configuration.h"
36 #include "status.h"
37 #include "digiscope.h"
38 #include "Viewer.h"
39 #include "qrunner.h"
40 
41 //=====================================================================
42 // Baudot support
43 //=====================================================================
44 
45 static char letters[32] = {
46 	'\0',	'E',	'\n',	'A',	' ',	'S',	'I',	'U',
47 	'\r',	'D',	'R',	'J',	'N',	'F',	'C',	'K',
48 	'T',	'Z',	'L',	'W',	'H',	'Y',	'P',	'Q',
49 	'O',	'B',	'G',	' ',	'M',	'X',	'V',	' '
50 };
51 
52 // U.S. version of the figures case.
53 static char figures[32] = {
54 	'\0',	'3',	'\n',	'-',	' ',	'\a',	'8',	'7',
55 	'\r',	'$',	'4',	'\'',	',',	'!',	':',	'(',
56 	'5',	'"',	')',	'2',	'#',	'6',	'0',	'1',
57 	'9',	'?',	'&',	' ',	'.',	'/',	';',	' '
58 };
59 
60 const double	view_rtty::SHIFT[] = {23, 85, 160, 170, 182, 200, 240, 350, 425, 850};
61 // FILTLEN must be same size as BAUD
62 const double	view_rtty::BAUD[]  = {45, 45.45, 50, 56, 75, 100, 110, 150, 200, 300};
63 const int		view_rtty::FILTLEN[] = { 512, 512, 512, 512, 512, 512, 512, 256, 128, 64};
64 const int		view_rtty::BITS[]  = {5, 7, 8};
65 const int		view_rtty::numshifts = (int)(sizeof(SHIFT) / sizeof(*SHIFT));
66 const int		view_rtty::numbauds = (int)(sizeof(BAUD) / sizeof(*BAUD));
67 
rx_init()68 void view_rtty::rx_init()
69 {
70 	for (int ch = 0; ch < progdefaults.VIEWERchannels; ch++) {
71 		channel[ch].state = IDLE;
72 		channel[ch].rxstate = RTTY_RX_STATE_IDLE;
73 		channel[ch].rxmode = LETTERS;
74 		channel[ch].phaseacc = 0;
75 		channel[ch].timeout = 0;
76 		channel[ch].frequency = NULLFREQ;
77 		channel[ch].poserr = channel[ch].negerr = 0.0;
78 
79 		channel[ch].mark_phase = 0;
80 		channel[ch].space_phase = 0;
81 		channel[ch].mark_mag = 0;
82 		channel[ch].space_mag = 0;
83 		channel[ch].mark_env = 0;
84 		channel[ch].space_env = 0;
85 
86 		channel[ch].inp_ptr = 0;
87 
88 		for (int i = 0; i < VIEW_MAXPIPE; i++)
89 			channel[ch].mark_history[i] =
90 			channel[ch].space_history[i] = cmplx(0,0);
91 	}
92 }
93 
init()94 void view_rtty::init()
95 {
96 	bool wfrev = wf->Reverse();
97 	bool wfsb = wf->USB();
98 	reverse = wfrev ^ !wfsb;
99 	rx_init();
100 }
101 
~view_rtty()102 view_rtty::~view_rtty()
103 {
104 	for (int ch = 0; ch < MAX_CHANNELS; ch ++) {
105 		if (channel[ch].mark_filt) delete channel[ch].mark_filt;
106 		if (channel[ch].space_filt) delete channel[ch].space_filt;
107 		if (channel[ch].bits) delete channel[ch].bits;
108 	}
109 }
110 
reset_filters(int ch)111 void view_rtty::reset_filters(int ch)
112 {
113 	delete channel[ch].mark_filt;
114 	channel[ch].mark_filt = new fftfilt(rtty_baud/samplerate, filter_length);
115 	channel[ch].mark_filt->rtty_filter(rtty_baud/samplerate);
116 	delete channel[ch].space_filt;
117 	channel[ch].space_filt = new fftfilt(rtty_baud/samplerate, filter_length);
118 	channel[ch].space_filt->rtty_filter(rtty_baud/samplerate);
119 }
120 
restart()121 void view_rtty::restart()
122 {
123 	double stl;
124 
125 	rtty_shift = shift = (progdefaults.rtty_shift < rtty::numshifts ?
126 			      SHIFT[progdefaults.rtty_shift] : progdefaults.rtty_custom_shift);
127 	rtty_baud = BAUD[progdefaults.rtty_baud];
128 	filter_length = FILTLEN[progdefaults.rtty_baud];
129 
130 	nbits = rtty_bits = BITS[progdefaults.rtty_bits];
131 	if (rtty_bits == 5)
132 		rtty_parity = RTTY_PARITY_NONE;
133 	else
134 		switch (progdefaults.rtty_parity) {
135 			case 0 : rtty_parity = RTTY_PARITY_NONE; break;
136 			case 1 : rtty_parity = RTTY_PARITY_EVEN; break;
137 			case 2 : rtty_parity = RTTY_PARITY_ODD; break;
138 			case 3 : rtty_parity = RTTY_PARITY_ZERO; break;
139 			case 4 : rtty_parity = RTTY_PARITY_ONE; break;
140 			default : rtty_parity = RTTY_PARITY_NONE; break;
141 		}
142 	rtty_stop = progdefaults.rtty_stop;
143 
144 
145 	symbollen = (int) (samplerate / rtty_baud + 0.5);
146 	bflen = symbollen/3;
147 
148 	set_bandwidth(shift);
149 
150 	rtty_BW = progdefaults.RTTY_BW;
151 
152 	bp_filt_lo = (shift/2.0 - rtty_BW/2.0) / samplerate;
153 	if (bp_filt_lo < 0) bp_filt_lo = 0;
154 	bp_filt_hi = (shift/2.0 + rtty_BW/2.0) / samplerate;
155 
156 	for (int ch = 0; ch < MAX_CHANNELS; ch ++) {
157 
158 		reset_filters(ch);
159 
160 		channel[ch].state = IDLE;
161 		channel[ch].timeout = 0;
162 		channel[ch].freqerr = 0.0;
163 		channel[ch].metric = 0.0;
164 		channel[ch].sigpwr = 0.0;
165 		channel[ch].noisepwr = 0.0;
166 		channel[ch].sigsearch = 0;
167 		channel[ch].frequency = NULLFREQ;
168 		channel[ch].counter = symbollen / 2;
169 		channel[ch].mark_phase = 0;
170 		channel[ch].space_phase = 0;
171 		channel[ch].mark_mag = 0;
172 		channel[ch].space_mag = 0;
173 		channel[ch].mark_env = 0;
174 		channel[ch].space_env = 0;
175 		channel[ch].inp_ptr = 0;
176 
177 		if (channel[ch].bits)
178 			channel[ch].bits->setLength(symbollen / 8);
179 		else
180 			channel[ch].bits = new Cmovavg(symbollen / 8);
181 
182 		channel[ch].mark_noise = channel[ch].space_noise = 0;
183 		channel[ch].bit = channel[ch].nubit = true;
184 
185 		for (int i = 0; i < VIEW_RTTY_MAXBITS; i++) channel[ch].bit_buf[i] = 0.0;
186 
187 		for (int i = 0; i < VIEW_MAXPIPE; i++)
188 			channel[ch].mark_history[i] = channel[ch].space_history[i] = cmplx(0,0);
189 	}
190 
191 // stop length = 1, 1.5 or 2 bits
192 	rtty_stop = progdefaults.rtty_stop;
193 	if (rtty_stop == 0) stl = 1.0;
194 	else if (rtty_stop == 1) stl = 1.5;
195 	else stl = 2.0;
196 	stoplen = (int) (stl * samplerate / rtty_baud + 0.5);
197 
198 	rx_init();
199 }
200 
view_rtty(trx_mode tty_mode)201 view_rtty::view_rtty(trx_mode tty_mode)
202 {
203 	cap |= CAP_AFC | CAP_REV;
204 
205 	mode = tty_mode;
206 
207 	samplerate = RTTY_SampleRate;
208 
209 	for (int ch = 0; ch < MAX_CHANNELS; ch ++) {
210 		channel[ch].mark_filt = (fftfilt *)0;
211 		channel[ch].space_filt = (fftfilt *)0;
212 		channel[ch].bits = (Cmovavg *)0;
213 	}
214 
215 	restart();
216 }
217 
mixer(double & phase,double f,cmplx in)218 cmplx view_rtty::mixer(double &phase, double f, cmplx in)
219 {
220 	cmplx z = cmplx( cos(phase), sin(phase)) * in;;
221 
222 	phase -= TWOPI * f / samplerate;
223 	if (phase < - TWOPI) phase += TWOPI;
224 
225 	return z;
226 }
227 
228 
bitreverse(unsigned char in,int n)229 unsigned char view_rtty::bitreverse(unsigned char in, int n)
230 {
231 	unsigned char out = 0;
232 
233 	for (int i = 0; i < n; i++)
234 		out = (out << 1) | ((in >> i) & 1);
235 
236 	return out;
237 }
238 
rparity(int c)239 static int rparity(int c)
240 {
241 	int w = c;
242 	int p = 0;
243 	while (w) {
244 		p += (w & 1);
245 		w >>= 1;
246 	}
247 	return p & 1;
248 }
249 
rttyparity(unsigned int c)250 int view_rtty::rttyparity(unsigned int c)
251 {
252 	c &= (1 << nbits) - 1;
253 
254 	switch (rtty_parity) {
255 	default:
256 	case RTTY_PARITY_NONE:
257 		return 0;
258 
259 	case RTTY_PARITY_ODD:
260 		return rparity(c);
261 
262 	case RTTY_PARITY_EVEN:
263 		return !rparity(c);
264 
265 	case RTTY_PARITY_ZERO:
266 		return 0;
267 
268 	case RTTY_PARITY_ONE:
269 		return 1;
270 	}
271 }
272 
decode_char(int ch)273 int view_rtty::decode_char(int ch)
274 {
275 	unsigned int parbit, par, data;
276 
277 	parbit = (channel[ch].rxdata >> nbits) & 1;
278 	par = rttyparity(channel[ch].rxdata);
279 
280 	if (rtty_parity != RTTY_PARITY_NONE && parbit != par)
281 		return 0;
282 
283 	data = channel[ch].rxdata & ((1 << nbits) - 1);
284 
285 	if (nbits == 5)
286 		return baudot_dec(ch & 0x7F, data);
287 
288 	return data;
289 }
290 
is_mark_space(int ch,int & correction)291 bool view_rtty::is_mark_space( int ch, int &correction)
292 {
293 	correction = 0;
294 // test for rough bit position
295 	if (channel[ch].bit_buf[0] && !channel[ch].bit_buf[symbollen-1]) {
296 // test for mark/space straddle point
297 		for (int i = 0; i < symbollen; i++)
298 			correction += channel[ch].bit_buf[i];
299 		if (abs(symbollen/2 - correction) < 6) // too small & bad signals are not decoded
300 			return true;
301 	}
302 	return false;
303 }
304 
is_mark(int ch)305 bool view_rtty::is_mark(int ch)
306 {
307 	return channel[ch].bit_buf[symbollen / 2];
308 }
309 
rx(int ch,bool bit)310 bool view_rtty::rx(int ch, bool bit)
311 {
312 	bool flag = false;
313 	unsigned char c = 0;
314 
315 	int correction = 0;
316 
317 	for (int i = 1; i < symbollen; i++)
318 		channel[ch].bit_buf[i-1] = channel[ch].bit_buf[i];
319 	channel[ch].bit_buf[symbollen - 1] = bit;
320 
321 	switch (channel[ch].rxstate) {
322 	case RTTY_RX_STATE_IDLE:
323 		if ( is_mark_space(ch, correction)) {
324 			channel[ch].rxstate = RTTY_RX_STATE_START;
325 			channel[ch].counter = correction;
326 		}
327 		break;
328 	case RTTY_RX_STATE_START:
329 		if (--channel[ch].counter == 0) {
330 			if (!is_mark(ch)) {
331 				channel[ch].rxstate = RTTY_RX_STATE_DATA;
332 				channel[ch].counter = symbollen;
333 				channel[ch].bitcntr = 0;
334 				channel[ch].rxdata = 0;
335 			} else {
336 				channel[ch].rxstate = RTTY_RX_STATE_IDLE;
337 			}
338 		}
339 		break;
340 	case RTTY_RX_STATE_DATA:
341 		if (--channel[ch].counter == 0) {
342 			channel[ch].rxdata |= is_mark(ch) << channel[ch].bitcntr++;
343 			channel[ch].counter = symbollen;
344 		}
345 		if (channel[ch].bitcntr == nbits + (rtty_parity != RTTY_PARITY_NONE ? 1 : 0))
346 			channel[ch].rxstate = RTTY_RX_STATE_STOP;
347 		break;
348 	case RTTY_RX_STATE_STOP:
349 		if (--channel[ch].counter == 0) {
350 			if (is_mark(ch)) {
351 				if (channel[ch].metric > rtty_squelch) {
352 					c = decode_char(ch);
353 // print this RTTY_CHANNEL
354 					if ( c != 0 )
355 						REQ(&viewaddchr, ch, (int)channel[ch].frequency, c, mode);
356 				}
357 				flag = true;
358 			}
359 			channel[ch].rxstate = RTTY_RX_STATE_IDLE;
360 		}
361 		break;
362 	default : break;
363 	}
364 
365 	return flag;
366 }
367 
Metric(int ch)368 void view_rtty::Metric(int ch)
369 {
370 	double delta = rtty_baud/2.0;
371 	double np = wf->powerDensity(channel[ch].frequency, delta) * 3000 / delta;
372 	double sp =
373 		wf->powerDensity(channel[ch].frequency - shift/2, delta) +
374 		wf->powerDensity(channel[ch].frequency + shift/2, delta) + 1e-10;
375 
376 	channel[ch].sigpwr = decayavg( channel[ch].sigpwr, sp, sp - channel[ch].sigpwr > 0 ? 2 : 16);
377 
378 	channel[ch].noisepwr = decayavg( channel[ch].noisepwr, np, 16 );
379 
380 	channel[ch].metric = CLAMP(channel[ch].sigpwr/channel[ch].noisepwr, 0.0, 100.0);
381 
382 	if (channel[ch].state == RCVNG)
383 		if (channel[ch].metric < rtty_squelch) {
384 			channel[ch].timeout = progdefaults.VIEWERtimeout * samplerate / WF_BLOCKSIZE;
385 			channel[ch].state = WAITING;
386 		}
387 
388 	if (channel[ch].timeout) {
389 		channel[ch].timeout--;
390 		if (!channel[ch].timeout) {
391 			channel[ch].frequency = NULLFREQ;
392 			channel[ch].metric = 0;
393 			channel[ch].freqerr = 0;
394 			channel[ch].state = IDLE;
395 			REQ(&viewclearchannel, ch);
396 		}
397 	}
398 }
399 
find_signals()400 void view_rtty::find_signals()
401 {
402 	double spwrhi = 0.0, spwrlo = 0.0, npwr = 0.0;
403 	double rtty_squelch = pow(10, progStatus.VIEWER_rttysquelch / 10.0);
404 	for (int i = 0; i < progdefaults.VIEWERchannels; i++) {
405 		if (channel[i].state != IDLE) continue;
406 		int cf = progdefaults.LowFreqCutoff + 100 * i;
407 		if (cf < shift) cf = shift;
408 		double delta = rtty_baud / 8;
409 		for (int chf = cf; chf < cf + 100 - rtty_baud / 4; chf += 5) {
410 			spwrlo = wf->powerDensity(chf - shift/2, delta);
411 			spwrhi = wf->powerDensity(chf + shift/2, delta);
412 			npwr = (wf->powerDensity(chf, delta) * 3000 / rtty_baud) + 1e-10;
413 			if ((spwrlo / npwr > rtty_squelch) && (spwrhi / npwr > rtty_squelch)) {
414 				if (!i && (channel[i+1].state == SRCHG || channel[i+1].state == RCVNG)) break;
415 				if ((i == (progdefaults.VIEWERchannels -2)) &&
416 					(channel[i+1].state == SRCHG || channel[i+1].state == RCVNG)) break;
417 				if (i && (channel[i-1].state == SRCHG || channel[i-1].state == RCVNG)) break;
418 				if (i > 3 && (channel[i-2].state == SRCHG || channel[i-2].state == RCVNG)) break;
419 				channel[i].frequency = chf;
420 				channel[i].sigsearch = SIGSEARCH;
421 				channel[i].state = SRCHG;
422 				REQ(&viewaddchr, i, (int)channel[i].frequency, 0, mode);
423 				break;
424 			}
425 		}
426 	}
427 	for (int i = 1; i < progdefaults.VIEWERchannels; i++ )
428 		if (fabs(channel[i].frequency - channel[i-1].frequency) < rtty_baud/2)
429 			clearch(i);
430 }
431 
clearch(int ch)432 void view_rtty::clearch(int ch)
433 {
434 	channel[ch].state = IDLE;
435 	channel[ch].rxstate = RTTY_RX_STATE_IDLE;
436 	channel[ch].rxmode = LETTERS;
437 	channel[ch].phaseacc = 0;
438 	channel[ch].frequency = NULLFREQ;
439 	channel[ch].poserr = channel[ch].negerr = 0.0;
440 	REQ( &viewclearchannel, ch);
441 }
442 
clear()443 void view_rtty::clear()
444 {
445 	for (int ch = 0; ch < progdefaults.VIEWERchannels; ch++) {
446 		channel[ch].state = IDLE;
447 		channel[ch].rxstate = RTTY_RX_STATE_IDLE;
448 		channel[ch].rxmode = LETTERS;
449 		channel[ch].phaseacc = 0;
450 		channel[ch].frequency = NULLFREQ;
451 		channel[ch].poserr = channel[ch].negerr = 0.0;
452 	}
453 }
454 
rx_process(const double * buf,int buflen)455 int view_rtty::rx_process(const double *buf, int buflen)
456 {
457 	cmplx z, zmark, zspace, *zp_mark, *zp_space;
458 	static bool bit = true;
459 	int n = 0;
460 
461 {
462 	reverse = wf->Reverse() ^ !wf->USB();
463 }
464 
465 	rtty_squelch = pow(10, progStatus.VIEWER_rttysquelch / 10.0);
466 
467 	for (int ch = 0; ch < progdefaults.VIEWERchannels; ch++) {
468 		if (channel[ch].state == IDLE)
469 			continue;
470 		if (channel[ch].sigsearch) {
471 			channel[ch].sigsearch--;
472 			if (!channel[ch].sigsearch)
473 				channel[ch].state = RCVNG;
474 		}
475 
476 		for (int len = 0; len < buflen; len++) {
477 			z = cmplx(buf[len], buf[len]);
478 
479 			zmark = mixer(channel[ch].mark_phase, channel[ch].frequency + shift/2.0, z);
480 			channel[ch].mark_filt->run(zmark, &zp_mark);
481 
482 			zspace = mixer(channel[ch].space_phase, channel[ch].frequency - shift/2.0, z);
483 			n = channel[ch].space_filt->run(zspace, &zp_space);
484 
485 // n loop
486 			if (n) Metric(ch);
487 
488 			for (int i = 0; i < n; i++) {
489 
490 				channel[ch].mark_mag = abs(zp_mark[i]);
491 				channel[ch].mark_env = decayavg (channel[ch].mark_env, channel[ch].mark_mag,
492 					(channel[ch].mark_mag > channel[ch].mark_env) ? symbollen / 4 : symbollen * 16);
493 				channel[ch].mark_noise = decayavg (channel[ch].mark_noise, channel[ch].mark_mag,
494 					(channel[ch].mark_mag < channel[ch].mark_noise) ? symbollen / 4 : symbollen * 48);
495 				channel[ch].space_mag = abs(zp_space[i]);
496 				channel[ch].space_env = decayavg (channel[ch].space_env, channel[ch].space_mag,
497 					(channel[ch].space_mag > channel[ch].space_env) ? symbollen / 4 : symbollen * 16);
498 				channel[ch].space_noise = decayavg (channel[ch].space_noise, channel[ch].space_mag,
499 					(channel[ch].space_mag < channel[ch].space_noise) ? symbollen / 4 : symbollen * 48);
500 
501 				channel[ch].noise_floor = min(channel[ch].space_noise, channel[ch].mark_noise);
502 
503 // clipped if clipped decoder selected
504 				double mclipped = 0, sclipped = 0;
505 				mclipped = channel[ch].mark_mag > channel[ch].mark_env ?
506 							channel[ch].mark_env : channel[ch].mark_mag;
507 				sclipped = channel[ch].space_mag > channel[ch].space_env ?
508 							channel[ch].space_env : channel[ch].space_mag;
509 				if (mclipped < channel[ch].noise_floor) mclipped = channel[ch].noise_floor;
510 				if (sclipped < channel[ch].noise_floor) sclipped = channel[ch].noise_floor;
511 
512 // Optimal ATC
513 //				int v = (((mclipped - channel[ch].noise_floor) * (channel[ch].mark_env - channel[ch].noise_floor) -
514 //						(sclipped - channel[ch].noise_floor) * (channel[ch].space_env - channel[ch].noise_floor)) -
515 //				0.25 * ((channel[ch].mark_env - channel[ch].noise_floor) *
516 //						(channel[ch].mark_env - channel[ch].noise_floor) -
517 //						(channel[ch].space_env - channel[ch].noise_floor) *
518 //						(channel[ch].space_env - channel[ch].noise_floor)));
519 //				bit = (v > 0);
520 // Kahn Square Law demodulator
521 				bit = norm(zp_mark[i]) >= norm(zp_space[i]);
522 
523 				channel[ch].mark_history[channel[ch].inp_ptr] = zp_mark[i];
524 				channel[ch].space_history[channel[ch].inp_ptr] = zp_space[i];
525 				channel[ch].inp_ptr = (channel[ch].inp_ptr + 1) % VIEW_MAXPIPE;
526 
527 				if (channel[ch].state == RCVNG && rx( ch, reverse ? !bit : bit ) ) {
528 					if (channel[ch].sigsearch) channel[ch].sigsearch--;
529 					int mp0 = channel[ch].inp_ptr - 2;
530 					int mp1 = mp0 + 1;
531 					if (mp0 < 0) mp0 += VIEW_MAXPIPE;
532 					if (mp1 < 0) mp1 += VIEW_MAXPIPE;
533 					double ferr = (TWOPI * samplerate / rtty_baud) *
534 						(!reverse ?
535 						arg(conj(channel[ch].mark_history[mp1]) * channel[ch].mark_history[mp0]) :
536 						arg(conj(channel[ch].space_history[mp1]) * channel[ch].space_history[mp0]));
537 					if (fabs(ferr) > rtty_baud / 2) ferr = 0;
538 					channel[ch].freqerr = decayavg ( channel[ch].freqerr, ferr / 4,
539 						progdefaults.rtty_afcspeed == 0 ? 8 :
540 						progdefaults.rtty_afcspeed == 1 ? 4 : 1 );
541 					if (channel[ch].metric > pow(10, progStatus.VIEWER_rttysquelch / 10.0))
542 						channel[ch].frequency -= ferr;
543 				}
544 			}
545 		}
546 	}
547 
548 	find_signals();
549 
550 	return 0;
551 }
552 
baudot_dec(int ch,unsigned char data)553 char view_rtty::baudot_dec(int ch, unsigned char data)
554 {
555 	int out = 0;
556 
557 	switch (data) {
558 	case 0x1F:		/* letters */
559 		channel[ch].rxmode = LETTERS;
560 		break;
561 	case 0x1B:		/* figures */
562 		channel[ch].rxmode = FIGURES;
563 		break;
564 	case 0x04:		/* unshift-on-space */
565 		if (progdefaults.UOSrx)
566 			channel[ch].rxmode = LETTERS;
567 		return ' ';
568 		break;
569 	default:
570 		if (channel[ch].rxmode == LETTERS)
571 			out = letters[data];
572 		else
573 			out = figures[data];
574 		break;
575 	}
576 
577 	return out;
578 }
579 
580 //=====================================================================
581 // RTTY transmit
582 //=====================================================================
583 
tx_process()584 int view_rtty::tx_process()
585 {
586 	return 0;
587 }
588 
589