3fbe18f75723b0a175ba35c467db5d4b118c5a36
[wdq2wav.git] / wdq2wav.cpp
1 /*****************************************************************************
2 ** FILE IDENTIFICATION
3 **
4 **   Name:          wdq2wav.cpp
5 **   Purpose:       Converts a channel of WinDAQ file to .WAV format
6 **   Programmer:    Kevin Rosenberg <kevin@rosenberg.net>
7 **   Date Started:  Jan 2003
8 **
9 **  Copyright (c) 2003 Kevin Rosenberg
10 **
11 **  $Id: wdq2wav.cpp,v 1.8 2003/01/21 07:37:13 kevin Exp $
12 **
13 **  This program is free software; you can redistribute it and/or modify
14 **  it under the terms of the GNU General Public License (version 2) as
15 **  published by the Free Software Foundation.
16 **
17 **  This program is distributed in the hope that it will be useful,
18 **  but WITHOUT ANY WARRANTY; without even the implied warranty of
19 **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 **  GNU General Public License for more details.
21 **
22 **  You should have received a copy of the GNU General Public License
23 **  along with this program; if not, write to the Free Software
24 **  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
25 ******************************************************************************/
26
27 #include <wdq2wav.h>
28
29 enum { O_VERBOSE, O_QUIET, O_DEBUG, O_HELP, O_VERSION };
30
31 static struct option my_options[] = 
32 {
33   {"verbose", 0, 0, O_VERBOSE},
34   {"quiet", 0, 0, O_QUIET},
35   {"debug", 0, 0, O_DEBUG},
36   {"help", 0, 0, O_HELP},
37   {"version", 0, 0, O_VERSION},
38   {0, 0, 0, 0}
39 };
40
41 static const char* g_szIdStr = "$Id: wdq2wav.cpp,v 1.8 2003/01/21 07:37:13 kevin Exp $";
42
43 static bool g_quiet = false;
44 static bool g_verbose = false;
45 static bool g_debug = false;
46
47 #define MAX_INPUT_STR 256
48
49 void
50 error_msg (const char *msg)
51 {
52   std::cerr << msg << "\n";
53 }
54
55 void
56 info_msg (const char* msg)
57 {
58   std::cout << msg << "\n";
59 }
60
61 void
62 info_msg_sans_newline (const char* msg)
63 {
64   std::cout << msg;
65 }
66
67 const char*
68 fileBasename (const char* filename)
69 {
70   const char* p = strrchr (filename, '/');
71   return (p ? p + 1 : filename);
72 }
73
74 char *
75 str_rm_tail (char *str, const char* const charlist)
76 {
77   int i;
78
79   for (i = strlen(str) - 1; i >= 0; i--)
80     if (strchr (charlist, str[i]) != NULL)
81       str[i] = 0;
82     else
83       break;            /* found non-specified char, all done */
84
85   return (str);
86 }
87
88 char *
89 str_wrm_tail (char *str)
90 {
91   return (str_rm_tail(str, "\b\t\n\r"));
92 }
93
94
95 void
96 usage (const char* progname)
97 {
98   std::cout << "usage: " << fileBasename (progname) << " <wdq-file> <channel-number> <wav-file>\n";
99   std::cout << "     --quiet     Supress all messages\n";
100   std::cout << "     --verbose   Verbose mode\n";
101   std::cout << "     --debug     Debug mode\n";
102   std::cout << "     --version   Print version\n";
103   std::cout << "     --help      Print this help message\n";
104 }
105
106 bool wdq2wav (const char* wdq_fname, const int channel, const char *wav_fname);
107
108 int
109 main (int argc, char *argv[])
110 {
111     while (1) {
112       int c = getopt_long (argc, argv, "", my_options, NULL);
113       if (c == -1)
114         break;
115       
116       switch (c) {
117       case O_VERSION:
118         std::cout << "Version " << g_szIdStr << std::endl;
119         break;
120       case O_QUIET:
121         g_quiet = true;
122         break;
123       case O_VERBOSE:
124         g_verbose = true;
125         break;
126       case O_DEBUG:
127         g_debug = true;
128         break;
129       case O_HELP:
130       case '?':
131         usage(argv[0]);
132         return (0);
133       default:
134         usage(argv[0]);
135         return (1);
136       }
137     }
138     
139     if (optind + 3 < argc) {
140       std::cerr << "Too many parameters\n";
141       usage (argv[0]);
142       return (1);
143     }
144
145     char wdq_fname[MAX_INPUT_STR];
146     if (optind < argc)
147       strncpy (wdq_fname, argv [optind], MAX_INPUT_STR);
148     else {
149       std::cout << "Enter input WinDAQ filename: ";
150       std::cin.getline (wdq_fname, MAX_INPUT_STR);
151     }
152
153     char channel_buf [MAX_INPUT_STR];
154     if (optind + 1 < argc)
155       strncpy (channel_buf, argv[optind+1], MAX_INPUT_STR);
156     else {
157       std::cout << "Enter channel number: ";
158       std::cin.getline (channel_buf, MAX_INPUT_STR);
159     }
160     
161     char *channel_endptr;
162     int channel = static_cast<int>(strtol (channel_buf, &channel_endptr, 10));
163     if (*channel_endptr != 0) {
164       std::ostringstream os;
165       os << "Error: Channel " << channel_buf << " is not an integer";
166       error_msg (os.str().c_str());
167       usage (argv[0]);
168       return (1);
169     }
170
171     char wav_fname[MAX_INPUT_STR];
172     if (optind + 2 < argc)
173       strncpy (wav_fname, argv[optind+2], MAX_INPUT_STR);
174     else {
175       std::cout << "Enter output wav filename: ";
176       std::cin.getline (wav_fname, MAX_INPUT_STR);
177     }
178       
179     if (! wdq2wav (wdq_fname, channel, wav_fname))
180       return 1;
181     
182     return 0;
183 }
184
185 bool
186 wdq2wav (const char* wdq_fname, const int channel, const char *wav_fname)
187 {
188   WindaqFile wdq (wdq_fname);
189
190   if (! wdq.ReadHeader()) {
191     if (wdq.m_error.size()) {
192       std::ostringstream os;
193       os << "Error reading file " << wdq_fname << ": " << wdq.m_error.c_str();
194       error_msg (os.str().c_str());
195     } else {
196       std::ostringstream os;
197       os << "Error reading file " << wdq_fname;
198       error_msg (os.str().c_str());
199     }
200     return false;
201   }
202   if (! g_quiet || g_verbose || g_debug) {
203     std::ostringstream os1;
204     os1 << "File " << wdq_fname;
205     info_msg (os1.str().c_str());
206     std::ostringstream os2;
207     time_t time = wdq.m_time_acq_start;
208     struct tm* tm = gmtime (&time);
209     os2 << "  Time File Creation: " << asctime(tm);
210     info_msg_sans_newline (os2.str().c_str());
211     std::ostringstream os3;
212     time = wdq.m_time_acq_stop;
213     tm = gmtime (&time);
214     os3 << "  Time File Written: " << asctime(tm);
215     info_msg_sans_newline (os3.str().c_str());
216     std::ostringstream os4;
217     os4 << "  Samples: " << wdq.m_nSamples <<
218       ", Channels: " << wdq.m_nChannels <<
219       ", Sample Rate: " << wdq.m_sample_rate;
220     info_msg (os4.str().c_str());
221   }
222   
223   WindaqChannel wdq_channel (wdq, channel);
224   if (! wdq_channel.m_valid) {
225     error_msg ("Error reading data from channel");
226     return false;
227   }
228
229   if (! g_quiet || g_verbose || g_debug) {
230     std::ostringstream os1;
231     os1 << "Channel " << channel;
232     info_msg (os1.str().c_str());
233     std::ostringstream os2;
234     os2 << "  Units: " << wdq_channel.m_units.c_str();
235     info_msg (os2.str().c_str());
236     std::ostringstream os3;
237     os3 << "  Raw minimum: " << wdq_channel.m_min_raw_data <<
238       ", maximum: " << wdq_channel.m_max_raw_data;
239     info_msg (os3.str().c_str());
240   }
241   if (g_debug) {
242     std::ostringstream os4;
243     os4 << "  Scaled minimum: " << wdq_channel.m_min_scaled_data <<
244       ", maximum: " << wdq_channel.m_max_scaled_data;
245     info_msg (os4.str().c_str());
246     std::ostringstream os5;
247     os5 << "  Slope " <<  wdq_channel.m_slope <<
248       ", Intercept " <<  wdq_channel.m_intercept;
249     info_msg (os5.str().c_str());
250   }
251
252   WavFile wav (wdq_channel, wav_fname);
253
254   if (! wav.m_valid) {
255     error_msg ("Error extracting wav from channel");
256     return false;
257   }
258
259   if (! wav.WriteFile ()) {
260     error_msg ("Error writing file");
261     return false;
262   }
263
264   return true;
265 }
266
267
268 WindaqFile::WindaqFile (const char* fname)
269   : m_fd(0), m_nChannels(0), m_nSamples(0), m_sample_rate(0), m_valid(false),
270     m_strFile (fname)
271 {
272 }
273
274 WindaqFile::~WindaqFile ()
275 {
276   if (m_fd != 0)
277     close (m_fd);
278 }
279
280 bool
281 WindaqFile::ReadHeader ()
282 {
283   unsigned short int tmp2;
284   unsigned int tmp4;
285
286   m_valid = false;
287   if ((m_fd = open (m_strFile.c_str(), O_RDONLY)) == 0) {
288     m_error = "Unable to open file";
289     return false;
290   }
291
292   lseek (0, 0, SEEK_SET);
293   if (read (m_fd, &tmp2, sizeof(tmp2)) != sizeof(tmp2)) {
294     m_error = "Unable to read beginning of file";
295     return false;
296   }
297   m_nChannels = tmp2 & 0x1f;
298   m_sr_denom = (tmp2 & 0x7fff) >> 5;
299   m_sr_numer = (tmp2 & 0x8000) << 1;
300   
301   if (read (m_fd, &tmp2, sizeof(tmp2)) != sizeof(tmp2))
302     return false;
303
304   m_sr_numer |= tmp2;
305   m_sample_rate = (double) m_sr_numer / (double) (m_sr_denom * m_nChannels);
306
307   if (read (m_fd, &tmp2, sizeof(tmp2)) != sizeof(tmp2))
308     return false;
309
310   m_channel_offset = tmp2 & 0xFF;
311   m_nBytes_channel_header = tmp2 >> 8;
312   
313   if (read (m_fd, &tmp2, sizeof(tmp2)) != sizeof(tmp2))
314     return false;
315
316   m_nHeader_bytes = tmp2;
317   
318   if (read (m_fd, &tmp4, sizeof(tmp4)) != sizeof(tmp4))
319     return false;
320
321   m_nData_bytes = tmp4;
322   m_nSamples = (m_nData_bytes / m_nChannels) / 2;
323
324   lseek (m_fd, 36, SEEK_SET);
325   if (read (m_fd, &tmp4, sizeof(tmp4)) != sizeof(tmp4))
326     return false;
327
328   m_time_acq_start = tmp4;
329   if (read (m_fd, &tmp4, sizeof(tmp4)) != sizeof(tmp4))
330     return false;
331
332   m_time_acq_stop = tmp4;
333   
334   // Verify Windaq signature
335   lseek (m_fd, m_nHeader_bytes - 2, SEEK_SET);
336   if (read (m_fd, &tmp2, sizeof(tmp2)) != sizeof(tmp2))
337     return false;
338
339   if (tmp2 != 0x8001) {
340     std::ostringstream os;
341     m_error = "File is not a valid WinDAQ file";
342     return false;
343   }
344
345   m_valid = true;
346   return true;
347 }
348
349   
350 WindaqChannel::WindaqChannel (WindaqFile& wdq, const int channel)
351   : r_wdq(wdq), m_data(0), m_slope(0), m_intercept (0), m_channel(channel),
352     m_valid(false)
353 {
354   if (wdq.m_valid) {
355     if (channel >= 1 && channel <= wdq.m_nChannels) {
356       m_valid = true;
357       read_channel_data();
358     } else {
359       std::ostringstream os;
360       os << "Channel " << channel << " is invalid, valid range 1-" <<
361         wdq.m_nChannels;
362       error_msg (os.str().c_str());
363     }
364   }
365 }
366
367 WindaqChannel::~WindaqChannel ()
368 {
369   if (m_data)
370     delete m_data;
371 }
372
373 bool
374 WindaqChannel::read_channel_data ()
375 {
376   unsigned short int tmp2;
377   unsigned int tmp4;
378   double float8;
379   
380   int fd = r_wdq.m_fd;
381
382   if (! m_valid)
383     return false;
384
385   m_data = new signed short int [r_wdq.m_nSamples * 2];
386
387   lseek (fd, r_wdq.m_channel_offset + 8 + 
388          (m_channel - 1) * r_wdq.m_nBytes_channel_header,
389          SEEK_SET);
390   if (read (fd, &float8, sizeof(float8)) != sizeof(float8)) {
391     error_msg ("Error reading file");
392     return false;
393   }
394   m_slope = float8;
395   if (read (fd, &float8, sizeof(float8)) != sizeof(float8)) {
396     error_msg ("Error reading file");
397     return false;
398   }
399   m_intercept = float8;
400
401   char units[7];
402   units[6] = 0;
403   if (read (fd, units, 6) != 6) {
404     error_msg ("Error reading file");
405     return false;
406   }
407   m_units = units;
408  
409   unsigned int row_bytes = 2 * r_wdq.m_nChannels;
410   signed short int sample_row [row_bytes];
411   
412   signed short int* psample = &sample_row[m_channel - 1];
413
414   lseek (fd, r_wdq.m_nHeader_bytes, SEEK_SET);
415   long int i;
416   signed short int data_max, data_min;
417   for (i = 0; i < r_wdq.m_nSamples; i++) {
418     if (read (fd, sample_row, row_bytes) != row_bytes) {
419       std::ostringstream os;
420       os << "Error reading file at " << i;
421       error_msg (os.str().c_str());
422       return false;
423     }
424     
425     signed short int value = *psample >> 2;
426     m_data[i] = value;
427     
428     if (i == 0) {
429       data_max = value;
430       data_min = value;
431     } else {
432       if (value > data_max)
433         data_max = value;
434       else if (value < data_min)
435         data_min = value;
436     }
437   }
438
439   m_max_raw_data = data_max;
440   m_min_raw_data = data_min;
441   m_max_scaled_data = (m_slope * data_max) + m_intercept;
442   m_min_scaled_data = (m_slope * data_min) + m_intercept;
443
444   return true;
445 }
446
447
448 WavFile::WavFile (WindaqChannel& wdq_channel, const char* fname)
449   : m_valid(false), m_data(0), m_nSamples(0), m_strFile(fname), m_fd(0)
450 {
451   if (wdq_channel.m_valid) {
452     m_nSamples = wdq_channel.r_wdq.m_nSamples;
453     m_nChannels = 1;
454     m_nBitsPerSample = 16;
455     m_nBytesPerSample = 2;
456     m_rate = wdq_channel.r_wdq.m_sample_rate;
457     m_data = new signed short int [m_nSamples];
458     
459     double data_offset = -wdq_channel.m_min_scaled_data;
460     double data_scale = 0.;
461     if (wdq_channel.m_max_scaled_data != wdq_channel.m_min_scaled_data)
462       data_scale = 65535. / (wdq_channel.m_max_scaled_data -
463                              wdq_channel.m_min_scaled_data);
464     
465     if (g_debug) {
466       std::ostringstream os;
467       os << "  Wav data_scale: " << data_scale << ", data_offset: " << data_offset;
468       info_msg (os.str().c_str());
469     }
470     
471     signed short int* input = wdq_channel.m_data;
472     double slope = wdq_channel.m_slope;
473     double intercept = wdq_channel.m_intercept;
474     
475     long int i;
476     for (i = 0; i < m_nSamples; i++) {
477       double value = input[i];
478       value = (slope * value) + intercept;
479       value = (value + data_offset) * data_scale;
480       value += 0.5 - 32768;
481       m_data[i] = static_cast<signed short int>(value);
482     }
483   }
484
485   m_valid = true;
486 }
487
488
489 WavFile::~WavFile ()
490 {
491   if (m_fd != 0)
492     close (m_fd);
493     
494   if (m_data != NULL)
495     delete m_data;
496 }
497
498 bool
499 WavFile::WriteFile ()
500 {
501   unsigned short int tmp2;
502   unsigned int tmp4;
503   unsigned long data_bytes = m_nSamples * 2;
504
505   if (! m_valid)
506     return false;
507
508   if (m_fd == 0)
509     if ((m_fd = open (m_strFile.c_str(), O_WRONLY | O_TRUNC | O_CREAT, 
510                       S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == 0) {
511       std::ostringstream os;
512       os << "Error opening output file " << m_strFile.c_str();
513       error_msg (os.str().c_str());
514       return false;
515     }
516
517   lseek (m_fd, 0, SEEK_SET);
518   if (write (m_fd, "RIFF", 4) != 4) {
519     error_msg ("Error writing file");
520     return false;
521   }
522
523   /* Length of file post 8 byte header */
524   tmp4 = 36 + data_bytes;
525   if (write (m_fd, &tmp4, sizeof(tmp4)) != sizeof(tmp4)) {
526     error_msg ("Error writing file");
527     return false;
528   }
529
530   if (write (m_fd, "WAVEfmt ", 8) != 8) {
531     error_msg ("Error writing file");
532     return false;
533   }
534   tmp4 = 0x10;
535   if (write (m_fd, &tmp4, sizeof(tmp4)) != sizeof(tmp4)) {
536     error_msg ("Error writing file");
537
538   }
539   tmp2 = 1;
540   if (write (m_fd, &tmp2, sizeof(tmp2)) != sizeof(tmp2)) {
541     error_msg ("Error writing file");
542     return false;
543   }
544   /* Number of channels */
545   tmp2 = m_nChannels;
546   if (write (m_fd, &tmp2, sizeof(tmp2)) != sizeof(tmp2)) {
547     error_msg ("Error writing file");
548     return false;
549   }
550   tmp4 = static_cast<int> (m_rate + 0.5);
551   /* Sample rate */
552   if (write (m_fd, &tmp4, sizeof(tmp4)) != sizeof(tmp4)) {
553     error_msg ("Error writing file");
554     return false;
555   }
556   tmp4 = static_cast<int> (m_rate * m_nBytesPerSample + 0.5);
557   /* Bytes per second */
558   if (write (m_fd, &tmp4, sizeof(tmp4)) != sizeof(tmp4)) {
559     error_msg ("Error writing file");
560     return false;
561   }
562   tmp2 = m_nBytesPerSample * m_nChannels;
563   /* Bytes per sample */
564   if (write (m_fd, &tmp2, sizeof(tmp2)) != sizeof(tmp2)) {
565     error_msg ("Error writing file");
566     return false;
567   }
568   tmp2 = m_nBitsPerSample;
569   /* Bits per sample */
570   if (write (m_fd, &tmp2, sizeof(tmp2)) != sizeof(tmp2)) {
571     error_msg ("Error writing file");
572     return false;
573   }
574   if (write (m_fd, "data", 4) != 4) {
575     error_msg ("Error writing file");
576     return false;
577   }
578   tmp4 = data_bytes;
579   /* Data length */
580   if (write (m_fd, &tmp4, sizeof(tmp4)) != sizeof(tmp4)) {
581     error_msg ("Error writing file");
582     return false;
583   }
584
585   long int i;
586   for (i = 0; i < m_nSamples; i++) {
587     signed short int stmp2 = m_data[i]; 
588     if (write (m_fd, &stmp2, sizeof(stmp2)) != sizeof(stmp2)) {
589       error_msg ("Error writing file");
590       return false;
591     }
592   }
593   
594   if (close (m_fd) < 0)
595     error_msg ("Error closing output file");
596   
597   m_fd = 0;
598   return true;
599 }
600
601
602   
603