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