/**************************************************************************** * * Programs for processing sound files in raw- or WAV-format. * -- Useful functions for parsing command line options and * issuing errors, warnings, and chit chat. * * Name: frame.c * Version: see static char *standardversion, below. * Author: Mark Roberts * Michael Labuschke sys_errlist fixes * ****************************************************************************/ /**************************************************************************** * These are useful functions that all DSP programs might find handy ****************************************************************************/ #include #include #include /* for exit and malloc */ #include #include #include #include #include #include "frame.h" time_t stopwatch; /* will hold time at start of calculation */ int samplefrequency; unsigned short samplewidth; unsigned short channels; int wavout; /* TRUE iff out file should be a .WAV file */ int iswav; /* TRUE iff in file was found to be a .WAV file */ FILE *in, *out; char *infilename, *outfilename; int verboselevel; char *version = ""; char *usage = ""; static int test_usage; static char *standardversion = "frame version 1.3, June 13th 2001"; static char *standardusage = "\nOptions common to all mark-dsp programs:\n" "-h \t\t create a WAV-header on output files.\n" "-c#\t\t set number of channels to # (1 or 2). Default: like input.\n" "-w#\t\t set number of bits per sample (width) to # (only 16)\n" "-f#\t\t set sample frequency to #. Default: like input.\n" "-V \t\t verbose: talk a lot.\n" "-Q \t\t quiet: talk as little as possible.\n\n" "In most cases, a filename of '-' means stdin or stdout.\n\n" "Bug-reports: mark@manumark.de\n" ; /* ----------------------------------------------------------------------- Writes the number of samples to result that are yet to be read from anyin. Return values are TRUE on success, FALSE on failure. -----------------------------------------------------------------------*/ int getremainingfilelength( FILE *anyin, long *result) { long i; i = ftell (anyin); if (i == -1) return FALSE; if (fseek (anyin, 0, SEEK_END) == -1) return FALSE; *result = ftell (anyin); if (*result == -1) return FALSE; (*result) -= i; (*result) /= samplewidth; if (fseek (anyin, i, SEEK_SET) == -1) return FALSE; return TRUE; } /* ----------------------------------------------------------------------- Read a .pk-header from 'anyin'. -----------------------------------------------------------------------*/ void readpkheader( FILE *anyin) { unsigned short tempushort; int tempint, i, x; unsigned char blood[8]; for (i = 0; i < 11; i++) { fread( &tempint, 4, 1, anyin); printf( "%d: %d, ", i, tempint); } printf( "\n"); fread( blood, 1, 8, anyin); for (i = 0; i < 8; i++) printf( "%d ", blood[i]); printf( "\n"); for (i = 0; i < 8; i++) { for (x = 128; x > 0; x /= 2) printf((blood[i] & x) == 0? "0 ":"1 "); printf(i%4==3? "\n":"| "); } printf( "\n"); for (i = 0; i < 2; i++) { fread( &tempint, 4, 1, anyin); printf( "%d: %d, ", i, tempint); } printf( "\n"); for (i = 0; i < 2; i++) { fread( &tempushort, 2, 1, anyin); printf( "%d: %d, ", i, tempushort); } printf( "\n"); } /* ----------------------------------------------------------------------- Read a .WAV header from 'anyin'. See header for details. -----------------------------------------------------------------------*/ void readwavheader( FILE *anyin) { unsigned int tempuint, sf; unsigned short tempushort, cn; char str[9]; int nowav = FALSE; iswav = FALSE; if (ftell(anyin) == -1) /* If we cannot seek this file */ { nowav = TRUE; /* -> Pretend this is no wav-file */ chat("File not seekable: not checking for WAV-header.\n"); } else { /* Expect four bytes "RIFF" and four bytes filelength */ fread (str, 1, 8, anyin); /* 0 */ str[4] = '\0'; if (strcmp(str, "RIFF") != 0) nowav = TRUE; /* Expect eight bytes "WAVEfmt " */ fread (str, 1, 8, anyin); /* 8 */ str[8] = '\0'; if (strcmp(str, "WAVEfmt ") != 0) nowav = TRUE; /* Expect length of fmt data, which should be 16 */ fread (&tempuint, 4, 1, anyin); /* 16 */ if (tempuint != 16) nowav = TRUE; /* Expect format tag, which should be 1 for pcm */ fread (&tempushort, 2, 1, anyin); /* 20 */ if (tempushort != 1) nowav = TRUE; /* Expect number of channels */ fread (&cn, 2, 1, anyin); /* 20 */ if (cn != 1 && cn != 2) nowav = TRUE; /* Read samplefrequency */ fread (&sf, 4, 1, anyin); /* 24 */ /* Read bytes per second: Should be samplefreq * channels * 2 */ fread (&tempuint, 4, 1, anyin); /* 28 */ if (tempuint != sf * cn * 2) nowav = TRUE; /* read bytes per frame: Should be channels * 2 */ fread (&tempushort, 2, 1, anyin); /* 32 */ if (tempushort != cn * 2) nowav = TRUE; /* Read bits per sample: Should be 16 */ fread (&tempushort, 2, 1, anyin); /* 34 */ if (tempushort != 16) nowav = TRUE; fread (str, 4, 1, anyin); /* 36 */ str[4] = '\0'; if (strcmp(str, "data") != 0) nowav = TRUE; fread (&tempuint, 4, 1, anyin); /* 40 */ if (nowav) { fseek (anyin, 0, SEEK_SET); /* Back to beginning of file */ chat("File has no WAV header.\n"); } else { samplefrequency = sf; channels = cn; chat ("Read WAV header: %d channels, samplefrequency %d.\n", channels, samplefrequency); iswav = TRUE; } } return; } /* ----------------------------------------------------------------------- Write a .WAV header to 'out'. See header for details. -----------------------------------------------------------------------*/ void makewavheader( void) { unsigned int tempuint, filelength; unsigned short tempushort; /* If fseek fails, don't create the header. */ if (fseek (out, 0, SEEK_END) != -1) { filelength = ftell (out); chat ("filelength %d, ", filelength); fseek (out, 0, SEEK_SET); fwrite ("RIFF", 1, 4, out); /* 0 */ tempuint = filelength - 8; fwrite (&tempuint, 4, 1, out); /* 4 */ fwrite ("WAVEfmt ", 1, 8, out); /* 8 */ /* length of fmt data 16 bytes */ tempuint = 16; fwrite (&tempuint, 4, 1, out); /* 16 */ /* Format tag: 1 for pcm */ tempushort = 1; fwrite (&tempushort, 2, 1, out); /* 20 */ chat ("%d channels\n", channels); fwrite (&channels, 2, 1, out); chat ("samplefrequency %d\n", samplefrequency); fwrite (&samplefrequency, 4, 1, out); /* 24 */ /* Bytes per second */ tempuint = channels * samplefrequency * 2; fwrite (&tempuint, 4, 1, out); /* 28 */ /* Block align */ tempushort = 2 * channels; fwrite (&tempushort, 2, 1, out); /* 32 */ /* Bits per sample */ tempushort = 16; fwrite (&tempushort, 2, 1, out); /* 34 */ fwrite ("data", 4, 1, out); /* 36 */ tempuint = filelength - 44; fwrite (&tempuint, 4, 1, out); /* 40 */ } return; } /* ----------------------------------------------------------------------- After all is read and done, inform the inclined user of the elapsed time -----------------------------------------------------------------------*/ static void statistics( void) { int temp; temp = time(NULL) - stopwatch; if (temp != 1) { inform ("\nTime: %d seconds\n", temp); } else { inform ("\nTime: 1 second\n"); } return; } /* ----------------------------------------------------------------------- Start the stopwatch and make sure the user is informed at end of program. -----------------------------------------------------------------------*/ void startstopwatch(void) { stopwatch = time(NULL); /* Remember time 'now' */ atexit(statistics); /* Call function statistics() at exit. */ return; } /* -------------------------------------------------------------------- Tests the character 'coal' for being a command line option character, momentarrily '-'. -------------------------------------------------------------------- */ int isoptionchar (char coal) { return (coal =='-'); } /* ----------------------------------------------------------------------- Reads through the arguments on the lookout for an option starting with 'string'. The rest of the option is read as a time and passed to *result, where the result is meant to mean 'number of samples' in that time. On failure, *result is unchanged. return value is TRUE on success, FALSE otherwise. -----------------------------------------------------------------------*/ int parsetimearg( int argcount, char *args[], char *string, int *result) { int i; if ((i = findoption( argcount, args, string)) > 0) { if (parsetime(args[i] + 1 + strlen( string), result)) return TRUE; argerrornum(args[i]+1, ME_NOTIME); } return FALSE; } /* ----------------------------------------------------------------------- The string argument is read as a time and passed to *result, where the result is meant to mean 'number of samples' in that time. On failure, *result is unchanged. return value is TRUE on success, FALSE otherwise. -----------------------------------------------------------------------*/ int parsetime(char *string, int *result) { int k; double temp; char m, s, end; k = sscanf(string, "%lf%c%c%c", &temp, &m, &s, &end); switch (k) { case 0: case EOF: case 4: return FALSE; case 1: *result = temp; break; case 2: if (m == 's') *result = temp * samplefrequency; else return FALSE; break; case 3: if (m == 'm' && s == 's') *result = temp * samplefrequency / 1000; else if (m == 'H' && s == 'z') *result = samplefrequency / temp; else return FALSE; break; default: argerrornum(NULL, ME_THISCANTHAPPEN); } return TRUE; } /* ----------------------------------------------------------------------- The string argument is read as a frequency and passed to *result, where the result is meant to mean 'number of samples' in one cycle of that frequency. On failure, *result is unchanged. return value is TRUE on success, FALSE otherwise. -----------------------------------------------------------------------*/ int parsefreq(char *string, double *result) { int k; double temp; char m, s, end; k = sscanf(string, "%lf%c%c%c", &temp, &m, &s, &end); switch (k) { case 0: case EOF: case 2: case 4: return FALSE; case 1: *result = temp; break; case 3: if (m == 'H' && s == 'z') *result = samplefrequency / temp; else return FALSE; break; default: argerrornum(NULL, ME_THISCANTHAPPEN); } return TRUE; } char *parsefilearg( int argcount, char *args[]) { int i; char *result = NULL; for (i = 1; i < argcount; i++) { if (args[i][0] != '\0' && (!isoptionchar (args[i][0]) || args[i][1] == '\0' )) { /*---------------------------------------------* * The argument is a filename: * * it is either no dash followed by something, * * or it is a dash following by nothing. * *---------------------------------------------*/ result = malloc( strlen( args[i]) + 1); if (result == NULL) fatalperror( "Couldn't allocate memory for filename\n"); strcpy( result, args[i]); args[i][0] = '\0'; /* Mark as used up */ break; } } return result; } int parseswitch( char *found, char *wanted) { if (strncmp( found, wanted, strlen( wanted)) == 0) { if (found[strlen( wanted)] == '\0') return TRUE; else argerrornum( found, ME_NOSWITCH); } return FALSE; } int parseswitcharg( int argcount, char *args[], char *string) { int i; if ((i = findoption( argcount, args, string)) > 0) { if (args[i][strlen( string) + 1] == '\0') return TRUE; else argerrornum( args[i] + 1, ME_NOSWITCH); } return FALSE; } int parseintarg( int argcount, char *args[], char *string, int *result) { int i, temp; char c; if ((i = findoption( argcount, args, string)) > 0) { switch (sscanf(args[i] + 1 + strlen( string), "%d%c", &temp, &c)) { case 0: case EOF: case 2: argerrornum(args[i]+1, ME_NOINT); return FALSE; case 1: *result = temp; break; default: say("frame.c: This can't happen\n"); } return TRUE; } else { return FALSE; } } /* -------------------------------------------------------------------- Reads through the arguments on the lookout for an option starting with 'string'. The rest of the option is read as a double and passed to *result. On failure, *result is unchanged. return value is TRUE on success, FALSE otherwise. -------------------------------------------------------------------- */ int parsedoublearg( int argcount, char *args[], char *string, double *result) { int i; double temp; char end; if ((i = findoption( argcount, args, string)) > 0) { switch (sscanf(args[i] + 1 + strlen( string), "%lf%c", &temp, &end)) { case 0: case EOF: case 2: argerrornum(args[i]+1, ME_NODOUBLE); return FALSE; case 1: *result = temp; break; default: say("frame.c: This can't happen\n"); } return TRUE; } else { return FALSE; } } /* -------------------------------------------------------------------- Reads through the arguments on the lookout for an option starting with 'string'. The rest of the option is read as a volume, i.e. absolute, percent or db. The result is passed to *result. On failure, *result is unchanged. return value is TRUE on success, FALSE otherwise. -------------------------------------------------------------------- */ int parsevolarg( int argcount, char *args[], char *string, double *result) { double vol = 1.0; char sbd, sbb, end; int i, weird = FALSE; if ((i = findoption( argcount, args, string)) > 0) { switch (sscanf(args[i] + 1 + strlen( string), "%lf%c%c%c", &vol, &sbd, &sbb, &end)) { case 0: case EOF: case 4: weird = TRUE; break; /* No number: error */ case 1: *result = vol; break; case 2: if (sbd == '%') *result = vol / 100; else weird = TRUE; /* One char but no percent: error */ break; case 3: if (sbd =='d' && sbb == 'b') *result = pow(2, vol / 6.02); else weird = TRUE; /* Two chars but not db: error */ break; default: say("frame.c: This can't happen.\n"); } if (weird) argerrornum( args[i] + 1, ME_NOVOL); /* ("Weird option: couldn't parse volume '%s'\n", args[i]+2); */ return !weird; } else { return FALSE; } } /* -------------------------------------------------------------------- Reads the specified string 's' and interprets it as a volume. The string would be of the form 1.8 or 180% or 5db. On success, the return value TRUE and *result is given result (i.e. the relative volume, i.e. 1.8). On failure, FALSE is returned and result is given value 1.0. -------------------------------------------------------------------- */ int parsevolume(char *s, double *result) { int k; char sbd, sbb, end; *result = 1.0; k = sscanf(s, "%lf%c%c%c", result, &sbd, &sbb, &end); switch (k) { case 0: case EOF: case 4: return FALSE; case 1: break; case 2: if (sbd != '%') return FALSE; (*result) /=100; break; case 3: if (sbd !='d' || sbb != 'b') return FALSE; (*result) = pow(2, (*result) / 6.02); break; default: say("parsevolume: This can't happen (%d).\n", k); } return TRUE; } /* -------------------------------------------------------------------- Reports an error due to parsing the string 's' encountered on the command line. -------------------------------------------------------------------- */ void argerror(char *s) { error ("Error parsing command line. Unrecognized option:\n\t-%s\n", s); fatalerror("\nTry --help for help.\n"); } /* -------------------------------------------------------------------- Reports an error due to parsing the string 's' encountered on the command line. 'code' indicates the type of error. -------------------------------------------------------------------- */ void argerrornum(char *s, Errornum code) { char *message; if (code == ME_TOOMANYFILES) { error("Too many files on command line: '%s'.\n", s); } else { if (s != NULL) error ("Error parsing option -%s:\n\t", s); switch( code) { case ME_NOINT: message = "Integer expected"; break; case ME_NODOUBLE: message = "Floating point number expected"; break; case ME_NOTIME: message = "Time argument expected"; break; case ME_NOVOL: message = "Volume argument expected"; break; case ME_NOSWITCH: message = "Garbage after switch-type option"; break; case ME_HEADERONTEXTFILE: message = "Option -h is not useful for text-output"; break; case ME_NOINFILE: message = "No input file specified"; break; case ME_NOOUTFILE: message = "No output file specified"; break; case ME_NOIOFILE: message = "No input/output file specified"; break; case ME_NOSTDIN: message = "Standard in not supported here"; break; case ME_NOSTDOUT: message = "Standard out not supported here"; break; case ME_NOSTDIO: message = "Standard in/out not supported here"; break; case ME_NOTENOUGHFILES: message = "Not enough files specified"; break; case ME_THISCANTHAPPEN: fatalerror("\nThis can't happen. Report this as a bug\n"); /* fatalerror does not return */ default: error("Error code %d not implemented. Fix me!\n", code); message = "Error message not implemented. Fix me!"; } error("%s\n", message); } fatalerror("\nTry --help for help.\n"); } /* -------------------------------------------------------------------- Reports an error due to parsing the string 's' encountered on the command line. 'message' explains the type of error. -------------------------------------------------------------------- */ void argerrortxt(char *s, char *message) { if (s != NULL) error ("Error parsing option -%s:\n\t", s); else error ("Error parsing command line:\n\t"); error ("%s\n", message); fatalerror("\nTry --help for help.\n"); } /* -------------------------------------------------------------------- Check for any remaining arguments and complain about their existence -------------------------------------------------------------------- */ void checknoargs( int argcount, char *args[]) { int i, errorcount = 0; for (i = 1; i < argcount; i++) { if (args[i][0] != '\0') /* An unused argument! */ { errorcount++; if (errorcount == 1) error("The following arguments were not recognized:\n"); error("\t%s\n", args[i]); } } if (errorcount > 0) /* Errors are fatal */ fatalerror("\nTry --help for help.\n"); return; /* No errors? Return. */ } /* -------------------------------------------------------------------- Parses the command line arguments as represented by the function arguments. Sets the global variables 'in', 'out', 'samplefrequency' and 'samplewidth' accordingly. Also verboselevel. The files 'in' and 'out' are even opened according to 'fileswitch'. See headerfile for details -------------------------------------------------------------------- */ void parseargs( int argcount, char *args[], int fileswitch) { char *filename; int tempint; if ((fileswitch & 1) != 0) /* If getting infile */ in = NULL; if ((fileswitch & 4) != 0) /* If getting outfile */ out = NULL; wavout = FALSE; verboselevel = 5; samplefrequency = DEFAULTFREQ; samplewidth = 2; channels = 1; /*-----------------------------------------------* * First first check testcase, usage and version * *-----------------------------------------------*/ test_usage = parseswitcharg( argcount, args, "-test-usage"); if (parseswitcharg( argcount, args, "-help")) { printf("%s%s", usage, standardusage); exit(0); } if (parseswitcharg( argcount, args, "-version")) { printf("%s\n(%s)\n", version, standardversion); exit(0); } /*--------------------------------------* * Set verboselevel * *--------------------------------------*/ while (parseswitcharg( argcount, args, "V")) verboselevel = 10; while (parseswitcharg( argcount, args, "Q")) verboselevel = 1; /*-------------------------------------------------* * Get filenames and open files * *-------------------------------------------------*/ if ((fileswitch & 1) != 0) /* Infile wanted */ { infilename = parsefilearg( argcount, args); if (infilename == NULL) argerrornum( NULL, ME_NOINFILE); if (strcmp( infilename, "-") == 0) { infilename = ""; in = stdin; if ((fileswitch & 2) != 0) /* Binfile wanted */ readwavheader( in); } else { if ((fileswitch & 2) == 0) /* Textfile wanted */ in = fopen(infilename, "rt"); else /* Binfile wanted */ if ((in = fopen(infilename, "rb")) != NULL) readwavheader( in); } if (in == NULL) fatalerror("Error opening input file '%s': %s\n", infilename,strerror(errno)); else inform("Using file '%s' as input\n", infilename); } if ((fileswitch & 4) != 0) /* Outfile wanted */ { outfilename = parsefilearg( argcount, args); if (outfilename == NULL) argerrornum( NULL, ME_NOOUTFILE); if (strcmp( outfilename, "-") == 0) { outfilename = ""; out = stdout; } else { if ((fileswitch & 8) == 0) /* Textfile wanted */ out = fopen(outfilename, "wt"); else /* Binfile wanted */ out = fopen(outfilename, "wb"); } if (out == NULL) fatalerror("Error opening output file '%s': %s\n", outfilename,strerror(errno)); else inform("Using file '%s' as output\n", outfilename); } if ((fileswitch & 32) != 0) /* In-/Outfile wanted */ { assert (in == NULL && out == NULL); infilename = outfilename = parsefilearg( argcount, args); if (outfilename == NULL) argerrornum( NULL, ME_NOIOFILE); if (strcmp( infilename, "-") == 0) argerrornum( infilename, ME_NOSTDIN); inform("Using file '%s' as input/output\n", outfilename); in = out = fopen(outfilename, "r+"); if (out == NULL) fatalerror("Error opening input/output file '%s': %s\n", outfilename,strerror(errno)); readwavheader( in); } if ((fileswitch & 16) == 0) /* No additional files wanted */ { if ((filename = parsefilearg( argcount, args)) != NULL) argerrornum( filename, ME_TOOMANYFILES); } /*-------------------------------------------------* * Set samplefrequency, width, wavout, *-------------------------------------------------*/ parseintarg( argcount, args, "f", &samplefrequency); wavout = parseswitcharg( argcount, args, "h"); if (parseintarg( argcount, args, "w", &tempint)) { if (tempint != 16) argerrortxt(NULL, "Option -w is only valid " "with value 16. Sorry."); else samplewidth = tempint; } if (parseintarg( argcount, args, "c", &tempint)) { if (tempint != 1 && tempint != 2) argerrortxt(NULL, "Option -c is only valid " "with values 1 or 2. Sorry."); else channels = tempint; } /*-------------------------------------------------* * Create WAV-header on output if wanted. * *-------------------------------------------------*/ if (wavout) switch (fileswitch & (12)) { case 4: /* User wants header on textfile */ argerrornum( NULL, ME_HEADERONTEXTFILE); case 12: /* User wants header on binfile */ makewavheader(); break; case 0: /* User wants header, but there is no outfile */ /* Problem: what about i/o-file, 32? You might want a header on that? Better ignore this case. */ break; case 8: /* An application musn't ask for this */ default: /* This can't happen */ assert( FALSE); } return; } /* -------------------------------------------------------------------- Returns the index 'i' of the first argument that IS an option, and which begins with the label 's'. If there is none, -1. We also mark that option as done with, i.e. we cross it out. -------------------------------------------------------------------- */ int findoption( int argcount, char *args[], char *s) { int i; if (test_usage) printf("Checking for option -%s\n", s); for (i=1; i 5) { va_start( ap, format); result = vfprintf( stderr, format, ap); va_end( ap); } return result; } int inform( const char *format, ...) { va_list ap; int result = 0; if (verboselevel > 1) { va_start( ap, format); result = vfprintf( stderr, format, ap); va_end( ap); } return result; } int error( const char *format, ...) { va_list ap; int result; va_start( ap, format); result = vfprintf( stderr, format, ap); va_end( ap); return result; } void fatalerror( const char *format, ...) { va_list ap; va_start( ap, format); vfprintf( stderr, format, ap); va_end( ap); myexit(1); } void fatalperror( const char *string) { perror( string); myexit( 1); } int say( const char *format, ...) { va_list ap; int result; va_start( ap, format); result = vfprintf( stdout, format, ap); va_end( ap); return result; } char *malloccopy( char *string) { char *result; result = malloc( strlen( string) + 1); if (result != NULL) strcpy( result, string); return result; } char *mallocconcat( char *one, char *two) { char *result; result = malloc( strlen( one) + strlen( two) + 1); if (result != NULL) { strcpy( result, one); strcat( result, two); } return result; } double double2db( double value) { if (value < 0) value = -value; return 6.0 * log( value / 32767) / log( 2); } void readawaysamples( FILE *in, size_t size) { short *buffer; int samplesread, count; buffer = malloc( sizeof( *buffer) * BUFFSIZE); if (buffer == NULL) fatalperror("Couldn't allocate buffer"); while (size > 0) { if (size > BUFFSIZE) count = BUFFSIZE; else count = size; samplesread = fread( buffer, sizeof(*buffer), count, in); if (ferror( in) != 0) fatalperror("Error reading input file"); size -= samplesread; } free( buffer); }