/* file: mit2wav.c G. Moody 12 February 2003 Last revised: 22 March 2018 ------------------------------------------------------------------------------- mit2wav: Convert WFDB format signal file(s) to .wav format Copyright (C) 2003-2010 George B. Moody This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . You may contact the author by e-mail (wfdb@physionet.org) or postal mail (MIT Room E25-505A, Cambridge, MA 02139 USA). For updates to this software, please visit PhysioNet (http://www.physionet.org/). _______________________________________________________________________________ This program converts a WFDB record into .wav format (format 16, multiplexed signals, with embedded header information). Use 'wav2mit' to perform the reverse conversion. You may be able to use this program's output to create analog signals by playing the .wav file through a sound card, but you should be aware of the following potential pitfalls: * Your sound card, and the software that comes with it, may not be able to play .wav files containing three or more signals. If this is a problem, you will need to extract one or two signals to include in the .wav file from your original recording (for example, using 'xform'). * Your sound card and its software may be unable to play .wav files at other than certain fixed sampling frequencies (typically 11025, 22050, and 44100 Hz). If this is a problem, you will need to resample the input at one of the frequencies supported by your sound card (for example, using 'xform') before converting it to .wav format using this program. * Your sound card may not be able to reproduce the frequencies present in the input. This is *very* likely if you are trying to recreate physiologic signals such as ECGs (with most of the useful information in the 0.1 to 30 Hz band) using a consumer sound card (which probably does not reproduce frequencies below the lower limit of human hearing (around 30 Hz). One possible solution to this problem is to create a digital signal containing a higher-frequency carrier signal, amplitude-modulated by the signal of interest, and to convert this AM signal into a .wav file; on playback, an analog AM demodulator would then recover the original low-frequency signal of interest. If you successfully implement this solution, please send details to wfdb@physionet.org. If the output contains 2, 3, 4, or 6 signals, and you play it through a sound card with at least that many outputs, the association between the digital inputs and the analog outputs is as follows: signals input output 2 0 left 1 right 3 0 left 1 right 2 center 4 0 front left -or- left 1 front right center 2 rear left right 3 rear right surround 6 0 left 1 left center 2 center 3 right center 4 right 5 surround Output files created by this program always have exactly three chunks (a header chunk, a format chunk, and a data chunk). Files created by other software in .wav format may include additional chunks. In .wav files, binary data are always written in little-endian format (least significant byte first). The format of mit2wav's output files is as follows: [Header chunk] Bytes 0 - 3: "RIFF" [4 ASCII characters] Bytes 4 - 7: L-8 (number of bytes to follow in the file, excluding bytes 0-7) Bytes 8 - 11: "WAVE" [4 ASCII characters] [Format chunk] Bytes 12 - 15: "fmt " [4 ASCII characters, note trailing space] Bytes 16 - 19: 16 (format chunk length in bytes, excluding bytes 12-19) Bytes 20 - 35: format specification, consisting of: Bytes 20 - 21: 1 (format tag, indicating no compression is used) Bytes 22 - 23: number of signals (1 - 65535) Bytes 24 - 27: sampling frequency in Hz (per signal) Note that the sampling frequency in a .wav file must be an integer multiple of 1 Hz, a restriction that is not imposed by MIT (WFDB) format. Bytes 28 - 31: bytes per second (sampling frequency * frame size in bytes) Bytes 32 - 33: frame size in bytes Bytes 34 - 35: bits per sample (ADC resolution in bits) Note that the actual ADC resolution (e.g., 12) is written in this field, although each output sample is right-padded to fill a full (16-bit) word. (.wav format allows for 8, 16, 24, and 32 bits per sample; all mit2wav output is 16 bits per sample.) [Data chunk] Bytes 36 - 39: "data" [4 ASCII characters] Bytes 40 - 43: L-44 (number of bytes to follow in the data chunk) Bytes 44 - L-1: sample data, consisting of: Bytes 44 - 45: sample 0, channel 0 Bytes 46 - 47: sample 0, channel 1 ... etc. (same order as in a multiplexed WFDB signal file) */ #include #include #ifdef NOMKSTEMP #define mkstemp mktemp #endif char *pname; /* name of this program, for use in error messages */ FILE *ofile; /* output (.wav) file pointer */ int out16(short); int out32(long); char *prog_name(char *); void help(void); main(int argc, char **argv) { char buf[80], *nrec = NULL, *ofname, *record = NULL, tfname[10]; double sps; FILE *sfile; int bitspersample = 0, bytespersecond, framelen, i, mag, nsig, *offset, *shift; long nsamp; static WFDB_Sample *x, *y; static WFDB_Siginfo *s; /* Interpret the command line. */ pname = prog_name(argv[0]); for (i = 1; i < argc; i++) { if (*argv[i] == '-') switch (*(argv[i]+1)) { case 'h': /* help requested */ help(); exit(1); break; case 'n': /* new record name follows */ if (++i >= argc) { (void)fprintf(stderr, "%s: new record name must follow -n\n", pname); exit(1); } nrec = argv[i]; break; case 'o': /* output file name follows */ if (++i >= argc) { (void)fprintf(stderr, "%s: name of wav-format output file must follow -o\n", pname); exit(1); } ofname = argv[i]; if (strlen(ofname)<5 || strcmp(".wav", ofname+strlen(ofname)-4)) { (void)fprintf(stderr, "%s: name of output file must end in '.wav'\n", pname); exit(1); } break; case 'r': if (++i >= argc) { (void)fprintf(stderr, "%s: record name must follow -r\n", pname); exit(1); } record = argv[i]; break; default: (void)fprintf(stderr, "%s: unrecognized option %s\n", pname, argv[i]); exit(1); } else { (void)fprintf(stderr, "%s: unrecognized argument %s\n", pname, argv[i]); exit(1); } } /* Check that required arguments are present and valid. */ if (ofname == NULL || record == NULL) { help(); exit(1); } if (nrec && strcmp(record, nrec) == 0) { (void)fprintf(stderr, "%s: names of input and output records must not be identical\n", pname); exit(1); } /* Open the input record and allocate memory for per-signal objects. */ if ((nsig = isigopen(record, NULL, 0)) <= 0) exit(2); if ((s = malloc(nsig * sizeof(WFDB_Siginfo))) == NULL || (offset = malloc(nsig * sizeof(int))) == NULL || (shift = malloc(nsig * sizeof(int))) == NULL || (x = malloc(nsig * sizeof(WFDB_Sample))) == NULL || (y = malloc(nsig * sizeof(WFDB_Sample))) == NULL) { (void)fprintf(stderr, "%s: insufficient memory\n", pname); exit(2); } if ((nsig = isigopen(record, s, nsig)) <= 0) exit(3); /* Check that the new record name is legal before proceeding. Note that this step creates a header file, which is overwritten after the processing is completed. */ if (nrec != NULL && newheader(nrec) < 0) exit(3); /* Set the output signal parameters. */ for (i = 0; i < nsig; i++) { if (s[i].adcres > bitspersample) bitspersample = s[i].adcres; offset[i] = s[i].adczero; s[i].adczero = 0; shift[i] = 16 - s[i].adcres; s[i].adcres = 16; mag = 1 << shift[i]; s[i].gain *= mag; s[i].baseline -= offset[i]; s[i].baseline *= mag; s[i].fname = ofname; s[i].group = 0; s[i].fmt = 16; s[i].spf = 1; } /* Get information needed for the header and format chunks. */ nsamp = strtim("e"); sps = strtim("1"); framelen = nsig * 2; bytespersecond = sps * framelen; /* Give up if the output cannot be written. */ if (osigfopen(s, (unsigned)nsig) < nsig) exit(4); for (i = 0; i < nsig; i++) wfdbsetstart(i, 44L); /* Create a temporary file for use below. */ (void)strcpy(tfname, "wavXXXXXX"); (void)mkstemp(tfname); if ((ofile = fopen(tfname, "wb")) == NULL) { (void)fprintf(stderr, "%s: can't create temporary file %s\n", pname, tfname); exit(1); } /* Write the header and format chunks, and the first 8 bytes of the data chunk, to the temporary file. */ if (fwrite("RIFF", 1, 4, ofile) != 4) { fprintf(stderr, "%s: can't write to %s\n", pname, ofname); exit(2); } out32(nsamp*framelen + 36); /* nsamp*framelen sample bytes, and 36 more bytes of miscellaneous embedded header */ fwrite("WAVEfmt ", 1, 8, ofile); out32(16); /* number of bytes to follow in format chunk */ out16(1); /* format tag */ out16(nsig); out32(sps); out32(sps*framelen); out16(framelen); out16(bitspersample); fwrite("data", 1, 4, ofile); out32(nsamp*framelen); fclose(ofile); /* Copy the input to the output. Reformatting is handled by putvec(). */ while (getvec(x) == nsig) { for (i = 0; i < nsig; i++) y[i] = (x[i] - offset[i]) << shift[i]; if (putvec(y) != nsig) break; } /* Write the new header file if requested. */ setsampfreq(sampfreq(NULL)); if (nrec) (void)newheader(nrec); /* Clean up. */ wfdbquit(); /* Prepend the temporary file to the new signal file. */ sprintf(buf, "mv %s %s.1", ofname, ofname); system(buf); sprintf(buf, "cat %s %s.1 >%s", tfname, ofname, ofname); system(buf); sprintf(buf, "rm -f %s %s.1", tfname, ofname); system(buf); exit(0); /*NOTREACHED*/ } int out16(short x) { if (putc(x & 0xff, ofile) == EOF || putc((x >> 8) & 0xff, ofile) == EOF) return (EOF); return (2); } int out32(long x) { if (putc(x & 0xff, ofile) == EOF || putc((x >> 8) & 0xff, ofile) == EOF || putc((x >> 16) & 0xff, ofile) == EOF || putc((x >> 24) & 0xff, ofile) == EOF) return (EOF); return (4); } char *prog_name(char *s) { char *p = s + strlen(s); #ifdef MSDOS while (p >= s && *p != '\\' && *p != ':') { if (*p == '.') *p = '\0'; /* strip off extension */ if ('A' <= *p && *p <= 'Z') *p += 'a' - 'A'; /* convert to lower case */ p--; } #else while (p >= s && *p != '/') p--; #endif return (p+1); } static char *help_strings[] = { "usage: %s -o FILE.wav -r RECORD [ OPTIONS ... ]\n", "where FILE.wav is the name of the wav-format output signal file,", "RECORD is the record name, and OPTIONS may include:", " -h print this usage summary", " -n NEWREC create a header file for the output signal file, so it", " may be read as record NEWREC", NULL }; void help() { int i; (void)fprintf(stderr, help_strings[0], pname); for (i = 1; help_strings[i] != NULL; i++) (void)fprintf(stderr, "%s\n", help_strings[i]); }