1 // ----------------------------------------------------------------------------
2 // view_cw.cxx
3 //
4 // (c) 2014  Mauri Niininen, AG1LE
5 // (c) 2017  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 // viewpsk is a multi channel psk decoder which allows the parallel processing
24 // of the complete audio spectrum from 400 to 1150 Hz in equal 25 Hz
25 // channels.  Each channel is separately decoded and the decoded characters
26 // passed to the user interface routines for presentation.  The number of
27 // channels can be up to and including 30.
28 
29 #include <config.h>
30 
31 #include <stdlib.h>
32 #include <stdio.h>
33 #include <iostream>
34 
35 #include "cw.h"
36 #include "view_cw.h"
37 
38 #include "pskeval.h"
39 #include "pskcoeff.h"
40 #include "pskvaricode.h"
41 #include "misc.h"
42 #include "configuration.h"
43 #include "Viewer.h"
44 #include "qrunner.h"
45 #include "status.h"
46 #include "waterfall.h"
47 
48 extern waterfall *wf;
49 
50 #define CH_SPACING 50
51 #define VCW_FFT_SIZE 2048 // must be a factor of 2
52 
53 enum {READY, NOT_READY};
54 
55 cMorse *CW_CHANNEL::morse = 0;
56 
CW_CHANNEL()57 CW_CHANNEL::CW_CHANNEL() {
58 	bitfilter.setLength(10);
59 	trackingfilter.setLength(16);
60 
61 	if (!morse) morse = new cMorse;
62 
63 	VCW_filter = new fftfilt ((CH_SPACING * 0.4) / VCW_SAMPLERATE, VCW_FFT_SIZE);
64 
65 	smpl_ctr = dec_ctr = 0;
66 	phi1 = phi2 = 0;
67 
68 	}
69 
~CW_CHANNEL()70 CW_CHANNEL::~CW_CHANNEL() {
71 	if (morse) {
72 		delete morse;
73 		morse = 0;
74 	}
75 	delete VCW_filter;
76 }
77 
init(int _ch,double _freq)78 void CW_CHANNEL::init(int _ch, double _freq)
79 {
80 	ch_freq = _freq;
81 	ch = _ch;
82 	phase = 0.0;
83 	phase_increment = TWOPI * ch_freq / VCW_SAMPLERATE;
84 	agc_peak = 0.0;
85 	sig_avg = 0.5;
86 	timeout = 0;
87 	smpl_ctr = 0;
88 	dec_ctr = 0;
89 	space_sent = true;
90 	last_element = 0;
91 	curr_element = 0;
92 	two_dots = 2 * VKWPM / progdefaults.CWspeed;
93 
94 	sync_parameters();
95 }
96 
reset()97 void CW_CHANNEL::reset()
98 {
99 	space_sent = true;
100 	last_element = 0;
101 	curr_element = 0;
102 	decode_str.clear();
103 }
104 
update_tracking(int dur_1,int dur_2)105 void CW_CHANNEL::update_tracking(int dur_1, int dur_2)
106 {
107 static int min_dot = (VKWPM / 60) / 2;
108 static int max_dash = 6 * VKWPM / 10;
109 	if ((dur_1 > dur_2) && (dur_1 > 4 * dur_2)) return;
110 	if ((dur_2 > dur_1) && (dur_2 > 4 * dur_1)) return;
111 	if (dur_1 < min_dot || dur_2 < min_dot) return;
112 	if (dur_2 > max_dash || dur_2 > max_dash) return;
113 
114 	two_dots = trackingfilter.run((dur_1 + dur_2) / 2);
115 }
116 
sample_count(unsigned int earlier,unsigned int later)117 inline int CW_CHANNEL::sample_count(unsigned int earlier, unsigned int later)
118 {
119 	return (earlier >= later) ? 0 : (later - earlier);
120 }
121 
sync_parameters()122 void CW_CHANNEL::sync_parameters()
123 {
124 	trackingfilter.reset();
125 }
126 
127 enum {KEYDOWN, KEYUP, POST_TONE, QUERY };
128 
decode_state(int cw_state)129 int CW_CHANNEL::decode_state(int cw_state)
130 {
131 	switch (cw_state) {
132 		case KEYDOWN:
133 		{
134 			if (cw_receive_state == KEYDOWN)
135 				return NOT_READY;
136 // first tone in idle state reset audio sample counter
137 			if (cw_receive_state == KEYUP) {
138 				smpl_ctr = 0;
139 				rx_rep_buf.clear();
140 			}
141 // save the timestamp
142 			cw_rr_start_timestamp = smpl_ctr;
143 // Set state to indicate we are inside a tone.
144 			cw_receive_state = KEYDOWN;
145 			return NOT_READY;
146 			break;
147 		}
148 		case KEYUP:
149 		{
150 // The receive state is expected to be inside a tone.
151 			if (cw_receive_state != KEYDOWN)
152 				return NOT_READY;
153 // Save the current timestamp
154 			curr_element = sample_count( cw_rr_start_timestamp, smpl_ctr );
155 			cw_rr_end_timestamp = smpl_ctr;
156 
157 // If the tone length is shorter than any noise cancelling
158 // threshold that has been set, then ignore this tone.
159 			if (curr_element < two_dots / 10) {
160 				cw_receive_state = KEYUP;
161 				return NOT_READY;
162 			}
163 
164 // Set up to track speed on dot-dash or dash-dot pairs for this test to
165 // work, we need a dot dash pair or a dash dot pair to validate timing
166 // from and force the speed tracking in the right direction.
167 			if (last_element > 0) update_tracking( last_element, curr_element );
168 
169 			last_element = curr_element;
170 // a dot is anything shorter than 2 dot times
171 			if (curr_element <= two_dots) {
172 				rx_rep_buf += '.';
173 			} else {
174 				rx_rep_buf += '-';
175 			}
176 // We just added a representation to the receive buffer.
177 // If it's full, then reset everything as it is probably noise
178 			if (rx_rep_buf.length() > MAX_MORSE_ELEMENTS) {
179 				cw_receive_state = KEYUP;
180 				rx_rep_buf.clear();
181 				smpl_ctr = 0;		// reset audio sample counter
182 				return NOT_READY;
183 			}
184 // All is well.  Move to the more normal after-tone state.
185 			cw_receive_state = POST_TONE;
186 			return NOT_READY;
187 			break;
188 		}
189 		case QUERY:
190 		{
191 // this should be called quite often (faster than inter-character gap) It looks after timing
192 // key up intervals and determining when a character, a word space, or an error char '*' should be returned.
193 // READY is returned when there is a printable character. Nothing to do if we are in a tone
194 			if (cw_receive_state == KEYDOWN)
195 				return NOT_READY;
196 // in this call we expect a pointer to a char to be valid
197 			if (cw_state == KEYDOWN || cw_state == KEYUP) {
198 // else we had no place to put character...
199 				cw_receive_state = KEYUP;
200 				rx_rep_buf.clear();
201 // reset decoding pointer
202 				return NOT_READY;
203 			}
204 // compute length of silence so far
205 //			sync_parameters();
206 			curr_element = sample_count( cw_rr_end_timestamp, smpl_ctr );
207 // SHORT time since keyup... nothing to do yet
208 			if (curr_element < two_dots)
209 				return NOT_READY;
210 // MEDIUM time since keyup... check for character space
211 // one shot through this code via receive state logic
212 			if ((curr_element > two_dots)
213 				&& (curr_element < 2 * two_dots) ) {
214 //				&& cw_receive_state == POST_TONE) {
215 
216 				std::string code = morse->rx_lookup(rx_rep_buf);
217 				if (code.empty()) {
218 					decode_str.clear();
219 					cw_receive_state = KEYUP;
220 					rx_rep_buf.clear();
221 					space_sent = false;
222 					return NOT_READY ;
223 				}
224 
225 				decode_str = code;
226 				cw_receive_state = KEYUP;
227 				rx_rep_buf.clear();
228 				space_sent = false;
229 				return READY;
230 			}
231 // LONG time since keyup... check for a word space
232 			if ((curr_element > 2 * two_dots) && !space_sent) {
233 				decode_str = " ";
234 				space_sent = true;
235 				return READY;
236 			}
237 			break;
238 		}
239 	}
240 	return NOT_READY;
241 }
242 
detect_tone()243 void CW_CHANNEL::detect_tone()
244 {
245 	norm_sig = 0;
246 	CWupper = 0;
247 	CWlower = 0;
248 
249 	sig_avg = decayavg(sig_avg, value, 1000);
250 
251 	if (value > sig_avg)  {
252 		if (value > agc_peak)
253 			agc_peak = decayavg(agc_peak, value, 100);
254 		else
255 			agc_peak = decayavg(agc_peak, value, 1000);
256 	}
257 
258 	if (!agc_peak) return;
259 
260 	value /= agc_peak;
261 	norm_sig = sig_avg / agc_peak;
262 
263 //	metric = 0.8 * metric;
264 //	metric += 0.2 * clamp(20*log10(sig_avg / noise_floor) , 0, 40);
265 	metric = clamp(20*log10(sig_avg / noise_floor) , 0, 40);
266 
267 	CWupper = norm_sig + 0.1;
268 	CWlower = norm_sig - 0.1;
269 
270 	if (metric > progStatus.VIEWER_cwsquelch ) {
271 
272 		if ((value >= CWupper) && (cw_receive_state != KEYDOWN))
273 			decode_state(KEYDOWN);
274 		if ((value < CWlower) && (cw_receive_state == KEYDOWN))
275 			decode_state(KEYUP);
276 		if ((decode_state(QUERY) == READY) ) {
277 			for (size_t n = 0; n < decode_str.length(); n++)
278 				REQ(&viewaddchr,
279 					ch,
280 					(int)ch_freq,
281 					decode_str[n], (int)MODE_CW);
282 			timeout = progdefaults.VIEWERtimeout * VPSKSAMPLERATE / WF_BLOCKSIZE;
283 			decode_str.clear();
284 			rx_rep_buf.clear();
285 		}
286 	}
287 }
288 
rx_process(const double * buf,int len)289 void CW_CHANNEL::rx_process(const double *buf, int len)
290 {
291 	cmplx z, *zp;
292 	int n = 0;
293 
294 	while (len-- > 0) {
295 
296 		z = cmplx ( *buf * cos(phase), *buf * sin(phase) );
297 		buf++;
298 
299 		phase += phase_increment;
300 		if (phase > TWOPI) phase -= TWOPI;
301 
302 		n = VCW_filter->run(z, &zp);
303 
304 		if (n) {
305 			for (int i = 0; i < n; i++) {
306 				if (++dec_ctr < VCW_DEC_RATIO) continue;
307 				dec_ctr = 0;
308 				smpl_ctr++;
309 				value = bitfilter.run(abs(zp[i]));
310 				detect_tone();
311 			}
312 		}
313 	}
314 }
315 
316 //======================================================================
317 // view_cw
318 //======================================================================
319 
view_cw()320 view_cw::view_cw()
321 {
322 	for (int i = 0; i < VCW_MAXCH; i++) channel[i].reset();
323 
324 	viewmode = MODE_CW;
325 }
326 
~view_cw()327 view_cw::~view_cw()
328 {
329 }
330 
init()331 void view_cw::init()
332 {
333 	nchannels = progdefaults.VIEWERchannels;
334 
335 	for (int i = 0; i < VCW_MAXCH; i++) {
336 		channel[i].init(i, 400.0 + CH_SPACING * i);
337 	}
338 	for (int i = 0; i < nchannels; i++)
339 		REQ(&viewclearchannel, i);
340 }
341 
restart()342 void view_cw::restart()
343 {
344 	for (int i = 0; i < VCW_MAXCH; i++) {
345 		channel[i].space_sent = true;
346 		channel[i].last_element = 0;
347 		channel[i].curr_element = 0;
348 		channel[i].two_dots = 2 * VKWPM / progdefaults.CWspeed;
349 	}
350 	init();
351 }
352 
clearch(int n)353 void view_cw::clearch(int n)
354 {
355 	REQ( &viewclearchannel, n);
356 	REQ( &viewaddchr, n, (int)NULLFREQ, 0, viewmode);
357 }
358 
clear()359 void view_cw::clear()
360 {
361 	for (int i = 0; i < VCW_MAXCH; i++) clearch(i);
362 }
363 
rx_process(const double * buf,int len)364 int view_cw::rx_process(const double *buf, int len)
365 {
366 	double nf = 1e8;
367 	if (nchannels != progdefaults.VIEWERchannels) init();
368 
369 	for (int n = 0; n < nchannels; n++) {
370 		channel[n].rx_process(buf, len);
371 
372 		if (nf > channel[n].avg_signal() &&
373 			channel[n].avg_signal() > 1e-3) nf = channel[n].avg_signal();
374 
375 		if (channel[n].timeout)
376 			if (! --channel[n].timeout)
377 				clearch(n);
378 	}
379 	if (nf <= 1e-3) nf = 1e-3;
380 	for (int n = 0; n < nchannels; n++) channel[n].set_noise_floor(nf);
381 
382 	return 0;
383 }
384 
get_freq(int n)385 int view_cw::get_freq(int n)
386 {
387 	return (int)channel[n].ch_freq;
388 }
389