1 // ----------------------------------------------------------------------------
2 // contestia.cxx -- CONTESTIA 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 // Copyright (C) 2005
10 // Tomi Manninen (oh2bns@sral.fi)
11 //
12 // This file is part of fldigi.
13 //
14 // Fldigi is free software: you can redistribute it and/or modify
15 // it under the terms of the GNU General Public License as published by
16 // the Free Software Foundation, either version 3 of the License, or
17 // (at your option) any later version.
18 //
19 // Fldigi is distributed in the hope that it will be useful,
20 // but WITHOUT ANY WARRANTY; without even the implied warranty of
21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 // GNU General Public License for more details.
23 //
24 // You should have received a copy of the GNU General Public License
25 // along with fldigi. If not, see <http://www.gnu.org/licenses/>.
26 // ----------------------------------------------------------------------------
27
28 #include <config.h>
29 #include <sstream>
30
31 #include <FL/Fl.H>
32
33 #include "contestia.h"
34 #include "modem.h"
35 #include "fl_digi.h"
36
37 #include "misc.h"
38 #include "confdialog.h"
39 #include "status.h"
40 #include "debug.h"
41
42 LOG_FILE_SOURCE(debug::LOG_MODEM);
43
44 using namespace std;
45
nco(double freq)46 double contestia::nco(double freq)
47 {
48 preamblephase += 2.0 * M_PI * freq / samplerate;
49
50 if (preamblephase > TWOPI)
51 preamblephase -= TWOPI;
52
53 return cos(preamblephase);
54 }
55
tx_init()56 void contestia::tx_init()
57 {
58 phaseacc = 0;
59 prevsymbol = cmplx (1.0, 0.0);
60 preamble = 32;
61 shreg = 0;
62
63 preamblesent = 0;
64 postamblesent = 0;
65 txbasefreq = get_txfreq_woffset();
66
67 rx_flush();
68
69 if (reverse) {
70 Tx->FirstCarrierMultiplier = (txbasefreq + (Tx->Bandwidth / 2)) / 500;
71 Tx->Reverse = 1;
72 } else {
73 Tx->FirstCarrierMultiplier = (txbasefreq - (Tx->Bandwidth / 2)) / 500;
74 Tx->Reverse = 0;
75 }
76
77 videoText();
78
79 Tx->Preset();
80 Tx->Start();
81 escape = 0;
82 }
83
rx_flush()84 void contestia::rx_flush()
85 {
86 unsigned char c;
87 Rx->Flush();
88 while (Rx->GetChar(c) > 0)
89 put_rx_char(c);
90 }
91
send_tones()92 void contestia::send_tones()
93 {
94 if (!progdefaults.contestia_start_tones) return;
95
96 double freqa, freqb;
97 tone_bw = bandwidth;
98 tone_midfreq = txbasefreq;
99
100 if (reverse) {
101 freqa = tone_midfreq + (tone_bw / 2.0);
102 freqb = tone_midfreq - (tone_bw / 2.0);
103 } else {
104 freqa = tone_midfreq - (tone_bw / 2.0);
105 freqb = tone_midfreq + (tone_bw / 2.0);
106 }
107
108 preamblephase = 0;
109 for (int i = 0; i < SR4; i++)
110 tonebuff[2*SR4 + i] = tonebuff[i] = nco(freqa) * ampshape[i];
111
112 preamblephase = 0;
113 for (int i = 0; i < SR4; i++)
114 tonebuff[3*SR4 + i] = tonebuff[SR4 + i] = nco(freqb) * ampshape[i];
115
116 for (int j = 0; j < TONE_DURATION; j += SCBLOCKSIZE)
117 ModulateXmtr(&tonebuff[j], SCBLOCKSIZE);
118
119 }
120
rx_init()121 void contestia::rx_init()
122 {
123 Rx->Reset();
124 escape = 0;
125 }
126
unescape(int c)127 int contestia::unescape(int c)
128 {
129 if (progdefaults.contestia8bit == 0)
130 return c;
131
132 if (escape) {
133 escape = 0;
134 return c + 128;
135 }
136
137 if (c == 127) {
138 escape = 1;
139 return -1;
140 }
141
142 return c;
143 }
144
tx_process()145 int contestia::tx_process()
146 {
147 int c = 0, len = 0;
148 unsigned char ch;
149
150 if (tones != progdefaults.contestiatones ||
151 bw != progdefaults.contestiabw ||
152 smargin != progdefaults.contestiasmargin ||
153 sinteg != progdefaults.contestiasinteg )
154 restart();
155
156 if (preamblesent != 1) {
157 send_tones();
158 preamblesent = 1;
159 // Olivia Transmitter class requires at least character
160 Tx->PutChar(0);
161 }
162
163 // The encoder works with BitsPerSymbol length blocks. If the
164 // modem already has that many characters buffered, don't try
165 // to read any more. If stopflag is set, we will always read
166 // whatever there is.
167 if (stopflag || (Tx->GetReadReady() < Tx->BitsPerSymbol)) {
168 if (!stopflag && (c = get_tx_char()) == GET_TX_CHAR_ETX)
169 stopflag = true;
170 if (stopflag)
171 Tx->Stop();
172 else {
173 if (c == GET_TX_CHAR_NODATA)
174 c = 0;
175 /* Replace un-representable characters with a dot */
176 if (c > (progdefaults.contestia8bit ? 255 : 127))
177 c = '.';
178 if (c > 127) {
179 c &= 127;
180 Tx->PutChar(127);
181 }
182 Tx->PutChar(c);
183 }
184 }
185
186 if (Tx->GetChar(ch) > 0)
187 if ((c = unescape(ch)) != -1)
188 put_echo_char(progdefaults.rx_lowercase ? tolower(c) : toupper(c));
189
190 if ((len = Tx->Output(txfbuffer)) > 0)
191 ModulateXmtr(txfbuffer, len);
192
193 if (stopflag && Tx->DoPostambleYet() == 1 && postamblesent != 1) {
194 postamblesent = 1;
195 send_tones();
196 }
197
198 if (!Tx->Running()) {
199 stopflag = false;
200 return -1;
201 }
202
203 return 0;
204 }
205
206
rx_process(const double * buf,int len)207 int contestia::rx_process(const double *buf, int len)
208 {
209 int c;
210 unsigned char ch = 0;
211 static double snr = 1e-3;
212 static char msg1[20];
213 static char msg2[20];
214
215 if (tones != progdefaults.contestiatones ||
216 bw != progdefaults.contestiabw ||
217 smargin != progdefaults.contestiasmargin ||
218 sinteg != progdefaults.contestiasinteg )
219 restart();
220
221 if ((lastfreq != frequency || Rx->Reverse) && !reverse) {
222 Rx->FirstCarrierMultiplier = (frequency - (Rx->Bandwidth / 2)) / 500;
223 Rx->Reverse = 0;
224 lastfreq = frequency;
225 Rx->Preset();
226 }
227 else if ((lastfreq != frequency || !Rx->Reverse) && reverse) {
228 Rx->FirstCarrierMultiplier = (frequency + (Rx->Bandwidth / 2)) / 500;
229 Rx->Reverse = 1;
230 lastfreq = frequency;
231 Rx->Preset();
232 }
233
234 Rx->SyncThreshold = progStatus.sqlonoff ?
235 clamp(progStatus.sldrSquelchValue / 5.0 + 3.0, 3.0, 90.0) : 3.0;
236
237 Rx->Process(buf, len);
238 sp = 0;
239 for (int i = frequency - Rx->Bandwidth/2; i < frequency - 1 + Rx->Bandwidth/2; i++)
240 if (wf->Pwr(i) > sp)
241 sp = wf->Pwr(i);
242 np = wf->Pwr(frequency + Rx->Bandwidth/2 + 2*Rx->Bandwidth/Rx->Tones);
243 if (np == 0) np = sp + 1e-8;
244 sigpwr = decayavg( sigpwr, sp, 10);
245 noisepwr = decayavg( noisepwr, np, 50);
246 snr = CLAMP(sigpwr / noisepwr, 0.001, 100000);
247
248 metric = clamp( 5.0 * (Rx->SignalToNoiseRatio() - 3.0), 0, 100);
249 display_metric(metric);
250
251 bool gotchar = false;
252 while (Rx->GetChar(ch) > 0) {
253 if ((c = unescape(ch)) != -1 && c > 7) {
254 put_rx_char(progdefaults.rx_lowercase ? tolower(c) : c);
255 gotchar = true;
256 }
257 }
258 if (gotchar) {
259 snprintf(msg1, sizeof(msg1), "s/n: %4.1f dB", 10*log10(snr) - 20);
260 put_Status1(msg1, 5, STATUS_CLEAR);
261 snprintf(msg2, sizeof(msg2), "f/o %+4.1f Hz", Rx->FrequencyOffset());
262 put_Status2(msg2, 5, STATUS_CLEAR);
263 }
264
265 return 0;
266 }
267
restart()268 void contestia::restart()
269 {
270 tones = progdefaults.contestiatones;
271 bw = progdefaults.contestiabw;
272 smargin = progdefaults.contestiasmargin;
273 sinteg = progdefaults.contestiasinteg;
274
275 samplerate = 8000;
276 bandwidth = 125 * (1 << bw);
277
278 Tx->Tones = 2 * (1 << tones);
279 Tx->Bandwidth = bandwidth;
280 Tx->SampleRate = samplerate;
281 Tx->OutputSampleRate = samplerate;
282 txbasefreq = get_txfreq_woffset();
283 Tx->bContestia = true;
284
285 if (reverse) {
286 Tx->FirstCarrierMultiplier = (txbasefreq + (Tx->Bandwidth / 2)) / 500;
287 Tx->Reverse = 1;
288 } else {
289 Tx->FirstCarrierMultiplier = (txbasefreq - (Tx->Bandwidth / 2)) / 500;
290 Tx->Reverse = 0;
291 }
292
293 if (Tx->Preset() < 0) {
294 LOG_ERROR("contestia: transmitter preset failed!");
295 return;
296 }
297
298 txbufferlen = Tx->MaxOutputLen;
299
300 if (txfbuffer) delete [] txfbuffer;
301 txfbuffer = new double[txbufferlen];
302
303 Rx->Tones = Tx->Tones;
304 Rx->Bandwidth = bandwidth;
305 Rx->SyncMargin = smargin;
306 Rx->SyncIntegLen = sinteg;
307 Rx->SyncThreshold = progStatus.sqlonoff ?
308 clamp(progStatus.sldrSquelchValue / 5.0 + 3.0, 0, 90.0) : 0.0;
309
310 Rx->SampleRate = samplerate;
311 Rx->InputSampleRate = samplerate;
312 Rx->bContestia = true;
313
314 if (reverse) {
315 Rx->FirstCarrierMultiplier = (frequency + (Rx->Bandwidth / 2)) / 500;
316 Rx->Reverse = 1;
317 } else {
318 Rx->FirstCarrierMultiplier = (frequency - (Rx->Bandwidth /2)) / 500;
319 Rx->Reverse = 0;
320 }
321
322 if (Rx->Preset() < 0) {
323 LOG_ERROR("contestia: receiver preset failed!");
324 return;
325 }
326 fragmentsize = 1024;
327 set_bandwidth(Tx->Bandwidth - Tx->Bandwidth / Tx->Tones);
328
329 stringstream info;
330 info << mode_info[mode].sname;
331 put_MODEstatus("%s", info.str().c_str());
332
333 metric = 0;
334
335 sigpwr = 1e-10; noisepwr = 1e-8;
336 LOG_DEBUG("\nContestia Rx parameters:\n%s", Rx->PrintParameters());
337 }
338
init()339 void contestia::init()
340 {
341 restart();
342 modem::init();
343 set_scope_mode(Digiscope::BLANK);
344
345 if (progdefaults.StartAtSweetSpot)
346 set_freq(progdefaults.PSKsweetspot);
347 else if (progStatus.carrier != 0) {
348 set_freq(progStatus.carrier);
349 #if !BENCHMARK_MODE
350 progStatus.carrier = 0;
351 #endif
352 } else
353 set_freq(wf->Carrier());
354
355 }
356
contestia(trx_mode omode)357 contestia::contestia(trx_mode omode)
358 {
359 mode = omode;
360 cap |= CAP_REV;
361
362 txfbuffer = 0;
363 samplerate = 8000;
364
365 switch (mode) {
366 case MODE_CONTESTIA_4_125:
367 progdefaults.contestiatones = tones = 1;
368 progdefaults.contestiabw = bw = 0;
369 REQ(set_contestia_tab_widgets);
370 break;
371 case MODE_CONTESTIA_4_250:
372 progdefaults.contestiatones = tones = 1;
373 progdefaults.contestiabw = bw = 1;
374 REQ(set_contestia_tab_widgets);
375 break;
376 case MODE_CONTESTIA_4_500:
377 progdefaults.contestiatones = tones = 1;
378 progdefaults.contestiabw = bw = 2;
379 REQ(set_contestia_tab_widgets);
380 break;
381 case MODE_CONTESTIA_4_1000:
382 progdefaults.contestiatones = tones = 1;
383 progdefaults.contestiabw = bw = 3;
384 REQ(set_contestia_tab_widgets);
385 break;
386 case MODE_CONTESTIA_4_2000:
387 progdefaults.contestiatones = tones = 1;
388 progdefaults.contestiabw = bw = 4;
389 REQ(set_contestia_tab_widgets);
390 break;
391 case MODE_CONTESTIA_8_125:
392 progdefaults.contestiatones = tones = 2;
393 progdefaults.contestiabw = bw = 0;
394 REQ(set_contestia_tab_widgets);
395 break;
396 case MODE_CONTESTIA_8_250:
397 progdefaults.contestiatones = tones = 2;
398 progdefaults.contestiabw = bw = 1;
399 REQ(set_contestia_tab_widgets);
400 break;
401 case MODE_CONTESTIA_8_500:
402 progdefaults.contestiatones = tones = 2;
403 progdefaults.contestiabw = bw = 2;
404 REQ(set_contestia_tab_widgets);
405 break;
406 case MODE_CONTESTIA_8_1000:
407 progdefaults.contestiatones = tones = 2;
408 progdefaults.contestiabw = bw = 3;
409 REQ(set_contestia_tab_widgets);
410 break;
411 case MODE_CONTESTIA_8_2000:
412 progdefaults.contestiatones = tones = 2;
413 progdefaults.contestiabw = bw = 4;
414 REQ(set_contestia_tab_widgets);
415 break;
416 case MODE_CONTESTIA_16_250:
417 progdefaults.contestiatones = tones = 3;
418 progdefaults.contestiabw = bw = 1;
419 REQ(set_contestia_tab_widgets);
420 break;
421 case MODE_CONTESTIA_16_500:
422 progdefaults.contestiatones = tones = 3;
423 progdefaults.contestiabw = bw = 2;
424 REQ(set_contestia_tab_widgets);
425 break;
426 case MODE_CONTESTIA_16_1000:
427 progdefaults.contestiatones = tones = 3;
428 progdefaults.contestiabw = bw = 3;
429 REQ(set_contestia_tab_widgets);
430 break;
431 case MODE_CONTESTIA_16_2000:
432 progdefaults.contestiatones = tones = 3;
433 progdefaults.contestiabw = bw = 4;
434 REQ(set_contestia_tab_widgets);
435 break;
436 case MODE_CONTESTIA_32_1000:
437 progdefaults.contestiatones = tones = 4;
438 progdefaults.contestiabw = bw = 3;
439 REQ(set_contestia_tab_widgets);
440 break;
441 case MODE_CONTESTIA_32_2000:
442 progdefaults.contestiatones = tones = 4;
443 progdefaults.contestiabw = bw = 4;
444 REQ(set_contestia_tab_widgets);
445 break;
446 case MODE_CONTESTIA_64_500:
447 progdefaults.contestiatones = tones = 5;
448 progdefaults.contestiabw = bw = 2;
449 REQ(set_contestia_tab_widgets);
450 break;
451 case MODE_CONTESTIA_64_1000:
452 progdefaults.contestiatones = tones = 5;
453 progdefaults.contestiabw = bw = 3;
454 REQ(set_contestia_tab_widgets);
455 break;
456 case MODE_CONTESTIA_64_2000:
457 progdefaults.contestiatones = tones = 5;
458 progdefaults.contestiabw = bw = 4;
459 REQ(set_contestia_tab_widgets);
460 break;
461 case MODE_CONTESTIA:
462 default:
463 tones = progdefaults.contestiatones;
464 bw = progdefaults.contestiabw;
465 REQ(set_contestia_tab_widgets);
466 break;
467 }
468
469 Tx = new MFSK_Transmitter< double >;
470 Rx = new MFSK_Receiver< double >;
471
472 Tx->bContestia = true;
473 Rx->bContestia = true;
474
475 lastfreq = 0;
476
477 for (int i = 0; i < SR4; i++) ampshape[i] = 1.0;
478 for (int i = 0; i < SR4 / 8; i++)
479 ampshape[i] = ampshape[SR4 - 1 - i] = 0.5 * (1.0 - cos(M_PI * i / (SR4/8)));
480
481 for (int i = 0; i < TONE_DURATION; i++) tonebuff[i] = 0;
482
483 tone_bw = -1;
484 tone_midfreq = -1;
485 }
486
~contestia()487 contestia::~contestia()
488 {
489 if (Tx) delete Tx;
490 if (Rx) delete Rx;
491 if (txfbuffer) delete [] txfbuffer;
492 }
493
494