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