1 // ----------------------------------------------------------------------------
2 // anal.cxx  --  anal modem
3 //
4 // Copyright (C) 2006-2009
5 //		Dave Freese, W1HKJ
6 //
7 // This file is part of fldigi.
8 //
9 // Modified by J C Gibbons / N8OBJ - May 2019
10 // Added info.txt file option for control of header - Feb 2020
11 // Added analysis file output modifications
12 // - Removed relative time from output file, added full ISO date/time stamp
13 // - Added keeping present days data is the file already exist when program started
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 <string>
32 #include <cstdio>
33 #include <ctime>
34 
35 #include "configuration.h"
36 #include "analysis.h"
37 #include "modem.h"
38 #include "misc.h"
39 #include "filters.h"
40 #include "fftfilt.h"
41 #include "digiscope.h"
42 #include "waterfall.h"
43 #include "main.h"
44 #include "fl_digi.h"
45 
46 #include "timeops.h"
47 #include "debug.h"
48 
49 // added for file support tasks 2-18-20 JC Gibbons N8OBJ
50 #include <iostream>
51 #include <fstream>
52 
53 using namespace std;
54 
55 static char msg1[80];
56 
tx_init()57 void anal::tx_init()
58 {
59 }
60 
rx_init()61 void anal::rx_init()
62 {
63 	phaseacc = 0;
64 	put_MODEstatus(mode);
65 }
66 
init()67 void anal::init()
68 {
69 	modem::init();
70 	rx_init();
71 	set_scope_mode(Digiscope::RTTY);
72 	rxcorr = progdefaults.RX_corr;
73 }
74 
~anal()75 anal::~anal()
76 {
77 	delete bpfilt;
78 	delete ffilt;
79 	delete afilt;
80 	progdefaults.RX_corr = rxcorr;
81 }
82 
83 // used for checking file exists function
84 #define F_OK 0
85 
createfilename()86 void anal::createfilename()
87 {
88 // Function to find or create the working directory [if not exist yet]
89 // also creates the filename that should be open for today's date [w and w/o full path]
90 // Create embedded date YYMMDD for file creation naming
91 	time_t now = time(NULL);
92 	gmtime_r(&now, &File_Start_Date);
93 
94 // create the embedded filename date image as YYMMDD
95 	strftime((char*)FileDate,sizeof(FileDate),"%y%m%d", &File_Start_Date);
96 
97 // create the embedded file date image as YYYY-MM-DD
98 	strftime((char*)FileData,sizeof(FileData),"%Y-%m-%d", &File_Start_Date);
99 
100 // create the new analysis file name only
101 	OpenAnalalysisFile.assign("analysis_").append(FileDate).append(".csv");
102 
103 // Full name with path
104 // Added new file naming and storage by N8OBJ 5-7-19
105 	analysisFilename.assign(AnalysisDir).append(OpenAnalalysisFile);
106 
107 }
108 
restart()109 void anal::restart()
110 {
111 	double fhi = ANAL_BW * 1.1 / samplerate;
112 	double flo = 0.0;
113 	if (bpfilt)
114 		bpfilt->create_filter(flo, fhi);
115 	else
116 		bpfilt = new fftfilt(flo, fhi, 2048);
117 
118 	set_bandwidth(ANAL_BW);
119 
120 	ffilt->reset();
121 	afilt->reset();
122 
123 	elapsed = 0.0;
124 	fout = 0.0;
125 	wf_freq = frequency;
126 
127 	if (clock_gettime(CLOCK_REALTIME, &start_time) == -1) {
128 		LOG_PERROR("clock_gettime");
129 		abort();
130 	}
131 
132 	passno = 0;
133 	dspcnt = DSP_CNT;
134 	for (int i = 0; i < PIPE_LEN; i++) pipe[i] = 0;
135 
136 	if (write_to_csv) stop_csv();
137 
138 	start_csv();
139 
140 }
141 
anal()142 anal::anal()
143 {
144 	mode = MODE_ANALYSIS;
145 
146 	samplerate = ANAL_SAMPLERATE;
147 
148 	bpfilt = (fftfilt *)0;
149 	ffilt = new Cmovavg(FILT_LEN * samplerate);
150 	afilt = new Cmovavg(FILT_LEN * samplerate);
151 
152 	createfilename();
153 
154 	cap &= ~CAP_TX;
155 	write_to_csv = false;
156 
157 	restart();
158 }
159 
clear_syncscope()160 void anal::clear_syncscope()
161 {
162 	set_scope(0, 0, false);
163 }
164 
mixer(cmplx in)165 cmplx anal::mixer(cmplx in)
166 {
167 	cmplx z = cmplx( cos(phaseacc), sin(phaseacc)) * in;
168 
169 	phaseacc -= TWOPI * frequency / samplerate;
170 	if (phaseacc < 0) phaseacc += TWOPI;
171 
172 	return z;
173 }
174 
start_csv()175 void anal::start_csv()
176 {
177 	string InfoPathname(AnalysisDir);
178 	InfoPathname.append("info.txt");
179 
180 	createfilename();
181 
182 //Open the data file for creation (write) operation
183 //first check to see if already created
184 
185 	if (fl_access(analysisFilename.c_str(), F_OK) == 0) {	// file exists! - use it and keep adding to it
186 	// indicate in status line that file write in progress
187 		write_to_csv = true;  //say to do writes to file
188 	} else {
189 		FILE *out = fl_fopen(analysisFilename.c_str(), "w"); //create new data file
190 		if (unlikely(!out)) {
191 			LOG_PERROR("fl_fopen");
192 			return;
193 		}
194 
195 		string InfoText;
196 		ifstream InfoTextFile( InfoPathname.c_str() );
197 		if (InfoTextFile.is_open()) {
198 // files exists, obtain info in text file
199 			getline (InfoTextFile, InfoText);
200 			InfoTextFile.close();
201 
202 // since file exists, write out full ISO date as first element of todays header
203 // along with the contents of info.txt for 1st line of header info
204 
205 			fprintf(out, "%s, %s\n", FileData, InfoText.c_str());
206 		}
207 // Always write out the normal column header to the new .csv file
208 		fprintf(out, "UTC, Freq, Freq Err, Vpk, dBV(Vpk)\n");
209 		fclose(out);
210 
211 		write_to_csv = true;
212 	}
213 }
214 
stop_csv()215 void anal::stop_csv()
216 {
217 	write_to_csv = false;
218 	put_status("");
219 }
220 
writeFile()221 void anal::writeFile()
222 {
223 	if (!write_to_csv) return;
224 
225 	time_t now = time(NULL);
226 	struct tm tm;
227 
228 // put check for date rollover here
229 	gmtime_r(&now, &tm);
230 	char DateNow [10];
231 
232 //   Create embedded date stamp in the YYMMDD format
233 	strftime((char*)DateNow,sizeof(DateNow),"%y%m%d", &tm);
234 //	printf("Date now is =%s\n",DateNow); //diag printout
235 
236 // check if date rolled over
237 	if (tm.tm_mday != File_Start_Date.tm_mday)
238 	{
239 		start_csv();
240 	}
241 	FILE *out = fl_fopen(analysisFilename.c_str(), "a");
242 	if (unlikely(!out)) {
243 		LOG_PERROR("fl_fopen");
244 		return;
245 	}
246 
247 // N8OBJ 5-7-19 changed 8.3f to 8.6f (more decimal places on signal strength - show uV level)
248 // Changed /added new .csv fields
249 //	header is: fprintf(out, "UTC,Freq,Freq Err,Vpk,dBV(Vpk)\n");
250 
251 	fprintf(out, "%02d:%02d:%02d, %13.3f, %6.3f, %8.6f, %6.2f\n",
252 		tm.tm_hour, tm.tm_min, tm.tm_sec,
253 		(wf->rfcarrier() + (wf->USB() ? 1.0 : -1.0) * (frequency + fout) + progdefaults.RIT), fout + progdefaults.RIT,
254 		amp, 20.0 * log10( (amp == 0 ? 1e-6 : amp) ) );
255 
256 	fclose(out);
257 
258 	char TimeNow[9];
259 	snprintf( TimeNow, sizeof(TimeNow), "%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec );
260 	put_Status1( TimeNow, 5, STATUS_CLEAR);
261 
262 	char StatusMsg [80];
263 	sprintf( StatusMsg, "File: %s", OpenAnalalysisFile.c_str());
264 	put_status(StatusMsg);
265 
266 }
267 
rx_process(const double * buf,int len)268 int anal::rx_process(const double *buf, int len)
269 {
270 	cmplx z, *zp;
271 	double fin;
272 	int n = 0;
273 
274 	if (wf_freq != frequency) {
275 		restart();
276 		set_scope(pipe, PIPE_LEN, false);
277 	}
278 
279 	for (int i = 0; i < len; i++) {
280 // create analytic signal from sound card input samples
281 		z = cmplx( *buf, *buf );
282 		buf++;
283 // mix it with the audio carrier frequency to create a baseband signal
284 		z = mixer(z);
285 // low pass filter using Windowed Sinc - Overlap-Add convolution filter
286 		n = bpfilt->run(z, &zp);
287 
288 		if (n) {
289 			for (int j = 0; j < n; j++) {
290 // measure phase difference between successive samples to determine
291 // the frequency of the baseband signal (+anal_baud or -anal_baud)
292 // see class cmplx definiton for operator %
293 			fin = arg( conj(prevsmpl) * zp[j] ) * samplerate / TWOPI;
294 			prevsmpl = zp[j];
295 // filter using moving average filter
296 			fout = ffilt->run(fin);
297 			amp  = afilt->run(abs(zp[j]));
298 			}
299 		} //else prevsmpl = z;
300 	}
301 
302 	if (passno++ > 10) {
303 		dspcnt -= (1.0 * n / samplerate);
304 
305 		if (dspcnt <= 0) {
306 			for (int i = 0; i < PIPE_LEN -1; i++)
307 				pipe[i] = pipe[i+1];
308 
309 			double fdsp = fout / 4.0;
310 			if (fabs(fdsp) < 2.6) {
311 				elapsed += DSP_CNT - dspcnt;
312 				pipe[PIPE_LEN - 1] = fout / 4.0;
313 				set_scope(pipe, PIPE_LEN, false);
314 
315 				if (wf->USB())
316 					snprintf(msg1, sizeof(msg1), "%13.3f", wf->rfcarrier() + frequency + fout + progdefaults.RIT);
317 				else
318 					snprintf(msg1, sizeof(msg1), "%13.3f", wf->rfcarrier() - frequency - fout + progdefaults.RIT);
319 				put_Status2(msg1, 2.0);
320 				writeFile();
321 			}
322 // reset the display counter & the pipe pointer
323 			dspcnt = DSP_CNT;
324 		}
325 	}
326 	return 0;
327 }
328 
329 //=====================================================================
330 // anal transmit
331 //=====================================================================
332 
tx_process()333 int anal::tx_process()
334 {
335 	return -1;
336 }
337