/* * July 5, 1991 * Copyright 1991 Lance Norskog And Sundry Contributors * This source code is freely redistributable and may be used for * any purpose. This copyright notice must be maintained. * Lance Norskog And Sundry Contributors are not responsible for * the consequences of using this software. */ #include "st.h" #if defined(unix) || defined(AMIGA) || defined(__OS2__) #include #include #endif #ifdef __STDC__ #include #else #include #endif #include #include #ifdef VMS #include #include #define LASTCHAR ']' #include "getopt.h" #else #include #define LASTCHAR '/' #endif /* YOU MAY WANT THIS #include */ /* * SOX main program. * * Rewrite for new nicer option syntax. July 13, 1991. * Rewrite for separate effects library. Sep. 15, 1991. */ #ifdef AMIGA /* This is the Amiga version string */ char amiversion[AmiVerSize]=AmiVerChars; #endif /* AMIGA */ EXPORT float volume = 1.0; /* expansion coefficient */ int dovolume = 0; int clipped = 0; /* Volume change clipping errors */ EXPORT float amplitude = 1.0; /* Largest sample so far */ EXPORT int writing = 0; /* are we writing to a file? */ /* export flags */ EXPORT int verbose = 0; /* be noisy on stderr */ EXPORT int summary = 0; /* just print summary of information */ long ibuf[BUFSIZ]; /* Intermediate processing buffer */ long obuf[BUFSIZ]; /* Intermediate processing buffer */ long volumechange(); #ifdef DOS char writebuf[BUFSIZ]; /* output write buffer */ #endif void gettype(), geteffect(), checkeffect(); EXPORT struct soundstream informat, outformat; char *myname; ft_t ft; struct effect eff; char *ifile, *ofile, *itype, *otype; /* IMPORT char *optarg; */ /* IMPORT int optind; */ extern char *optarg; extern int optind; main(n, args) int n; char **args; { myname = args[0]; init(); ifile = ofile = NULL; /* Get input format options */ ft = &informat; doopts(n, args); /* Get input file */ if (optind >= n) #ifndef VMS usage("No input file?"); #else /* I think this is the VMS patch, but am not certain */ fail("Input file required"); #endif ifile = args[optind]; if (! strcmp(ifile, "-")) ft->fp = stdin; else if ((ft->fp = fopen(ifile, READBINARY)) == NULL) fail("Can't open input file '%s': %s", ifile, strerror(errno)); ft->filename = ifile; optind++; /* Let -e allow no output file, just do an effect */ if (optind < n) { if (strcmp(args[optind], "-e")) { /* Get output format options */ ft = &outformat; doopts(n, args); /* Get output file */ if (optind >= n) usage("No output file?"); ofile = args[optind]; ft->filename = ofile; /* * There are two choices here: * 1) stomp the old file - normal shell "> file" behavior * 2) fail if the old file already exists - csh mode */ if (! strcmp(ofile, "-")) ft->fp = stdout; else { #ifdef unix /* * Remove old file if it's a text file, but * preserve Unix /dev/sound files. I'm not sure * this needs to be here, but it's not hurting * anything. */ if ((ft->fp = fopen(ofile, WRITEBINARY)) && (filetype(fileno(ft->fp)) == S_IFREG)) { fclose(ft->fp); unlink(ofile); creat(ofile, 0666); ft->fp = fopen(ofile, WRITEBINARY); } #else ft->fp = fopen(ofile, WRITEBINARY); #endif if (ft->fp == NULL) fail("Can't open output file '%s': %s", ofile, strerror(errno)); #ifdef DOS if (setvbuf (ft->fp,writebuf,_IOFBF,sizeof(writebuf))) fail("Can't set write buffer"); #endif } writing = 1; } optind++; } /* ??? */ /* if ((optind < n) && !writing && !eff.name) fail("Can't do an effect without an output file!"); */ /* Get effect name */ if (optind < n) { eff.name = args[optind]; optind++; geteffect(&eff); (* eff.h->getopts)(&eff, n - optind, &args[optind]); } /* * If we haven't specifically set an output file * don't write a file; we could be doing a summary * or a format check. */ /* if (! ofile) usage("Must be given an output file name"); */ if (! ofile) writing = 0; /* Check global arguments */ if (volume <= 0.0) fail("Volume must be greater than 0.0"); #if defined(unix) || defined(AMIGA) informat.seekable = (filetype(fileno(informat.fp)) == S_IFREG); outformat.seekable = (filetype(fileno(outformat.fp)) == S_IFREG); #else #if defined(DOS) || defined(__OS2__) informat.seekable = 1; outformat.seekable = 1; #else informat.seekable = 0; outformat.seekable = 0; #endif #endif /* If file types have not been set with -t, set from file names. */ if (! informat.filetype) { if (informat.filetype = strrchr(ifile, LASTCHAR)) informat.filetype++; else informat.filetype = ifile; if (informat.filetype = strrchr(informat.filetype, '.')) informat.filetype++; else /* Default to "auto" */ informat.filetype = "auto"; } if (writing && ! outformat.filetype) { if (outformat.filetype = strrchr(ofile, LASTCHAR)) outformat.filetype++; else outformat.filetype = ofile; if (outformat.filetype = strrchr(outformat.filetype, '.')) outformat.filetype++; } /* Default the input comment to the filename. * The output comment will be assigned when the informat * structure is copied to the outformat. */ informat.comment = informat.filename; process(); statistics(); exit(0); } doopts(n, args) int n; char **args; { int c; char *str; while ((c = getopt(n, args, "r:v:t:c:suUAbwlfdDxSV")) != -1) { switch(c) { case 't': if (! ft) usage("-t"); ft->filetype = optarg; if (ft->filetype[0] == '.') ft->filetype++; break; case 'r': if (! ft) usage("-r"); str = optarg; if ((! sscanf(str, "%lu", &ft->info.rate)) || (ft->info.rate <= 0)) fail("-r must be given a positive integer"); break; case 'v': if (! ft) usage("-v"); str = optarg; if ((! sscanf(str, "%e", &volume)) || (volume <= 0)) fail("Volume value '%s' is not a number", optarg); dovolume = 1; break; case 'c': if (! ft) usage("-c"); str = optarg; if (! sscanf(str, "%d", &ft->info.channels)) fail("-c must be given a number"); break; case 'b': if (! ft) usage("-b"); ft->info.size = BYTE; break; case 'w': if (! ft) usage("-w"); ft->info.size = WORD; break; case 'l': if (! ft) usage("-l"); ft->info.size = LONG; break; case 'f': if (! ft) usage("-f"); ft->info.size = FLOAT; break; case 'd': if (! ft) usage("-d"); ft->info.size = DOUBLE; break; case 'D': if (! ft) usage("-D"); ft->info.size = IEEE; break; case 's': if (! ft) usage("-s"); ft->info.style = SIGN2; break; case 'u': if (! ft) usage("-u"); ft->info.style = UNSIGNED; break; case 'U': if (! ft) usage("-U"); ft->info.style = ULAW; break; case 'A': if (! ft) usage("-A"); ft->info.style = ALAW; break; case 'x': if (! ft) usage("-x"); ft->swap = 1; break; /* stat effect does this ? case 'S': summary = 1; break; */ case 'V': verbose = 1; break; } } } init() { /* init files */ informat.info.rate = outformat.info.rate = 0; informat.info.size = outformat.info.size = -1; informat.info.style = outformat.info.style = -1; informat.info.channels = outformat.info.channels = -1; informat.comment = outformat.comment = NULL; informat.swap = 0; informat.filetype = outformat.filetype = (char *) 0; informat.fp = stdin; outformat.fp = stdout; informat.filename = "input"; outformat.filename = "output"; } /* * Process input file -> effect -> output file * one buffer at a time */ process() { long isamp, osamp, istart; long i, idone, odone; gettype(&informat); if (writing) gettype(&outformat); /* Read and write starters can change their formats. */ (* informat.h->startread)(&informat); checkformat(&informat); report("Input file: using sample rate %lu\n\tsize %s, style %s, %d %s", informat.info.rate, sizes[informat.info.size], styles[informat.info.style], informat.info.channels, (informat.info.channels > 1) ? "channels" : "channel"); if (informat.comment) report("Input file: comment \"%s\"\n", informat.comment); if (writing) { copyformat(&informat, &outformat); (* outformat.h->startwrite)(&outformat); checkformat(&outformat); cmpformats(&informat, &outformat); report("Output file: using sample rate %lu\n\tsize %s, style %s, %d %s", outformat.info.rate, sizes[outformat.info.size], styles[outformat.info.style], outformat.info.channels, (outformat.info.channels > 1) ? "channels" : "channel"); if (outformat.comment) report("Output file: comment \"%s\"\n", outformat.comment); } /* Very Important: * Effect fabrication and start is called AFTER files open. * Effect may write out data beforehand, and * some formats don't know their sample rate until now. */ checkeffect(0); /* inform effect about signal information */ eff.ininfo = informat.info; eff.outinfo = outformat.info; (* eff.h->start)(&eff); istart = 0; while((isamp = (*informat.h->read)(&informat,&ibuf[istart], (long) BUFSIZ-istart))>0) { long *ib = ibuf; isamp += istart; /* Do volume before effect or after? idunno */ if (dovolume) for (i = 0; i < isamp; i++) ibuf[i] = volumechange(ibuf[i]); osamp = sizeof(obuf) / sizeof(long); /* Effect (i.e. rate change) may do different sizes I and O */ while (isamp) { idone = isamp; odone = osamp; (* eff.h->flow)(&eff, ib, obuf, &idone, &odone); /* * kludge! * Effect is stuck. Start over with new buffer. * This can drop samples at end of file. * No effects currently do this, but it could happen. */ if (idone == 0) { int i; for(i = isamp - 1; i; i--) ibuf[i] = ib[i]; istart = isamp; isamp = 0; break; } if (writing && (odone > 0)) (* outformat.h->write)(&outformat, obuf, (long) odone); isamp -= idone; ib += idone; } } /* Drain effect out */ if (writing) { odone = sizeof(obuf) / sizeof(long); (* eff.h->drain)(&eff, obuf, &odone); if (odone > 0) (* outformat.h->write)(&outformat, obuf, (long) odone); /* keep calling it until it returns a partial buffer */ while (odone == (sizeof(obuf) / sizeof(long))) { (* eff.h->drain)(&eff, obuf, &odone); if (odone) (* outformat.h->write)(&outformat, obuf, (long) odone); } } /* Very Important: * Effect stop is called BEFORE files close. * Effect may write out more data after. */ (* eff.h->stop)(&eff); (* informat.h->stopread)(&informat); fclose(informat.fp); if (writing) (* outformat.h->stopwrite)(&outformat); if (writing) fclose(outformat.fp); } /* * Check that we have a known format suffix string. */ void gettype(formp) ft_t formp; { char **list; int i; if (! formp->filetype) fail("Must give file type for %s file, either as suffix or with -t option", formp->filename); for(i = 0; formats[i].names; i++) { for(list = formats[i].names; *list; list++) { char *s1 = *list, *s2 = formp->filetype; if (! strcmpcase(s1, s2)) break; /* not a match */ } if (! *list) continue; /* Found it! */ formp->h = &formats[i]; return; } if (! strcmpcase(formp->filetype, "snd")) { verbose = 1; report("File type '%s' is used to name several different formats.", formp->filetype); report("If the file came from a Macintosh, it is probably"); report("a .ub file with a sample rate of 11025 (or possibly 5012 or 22050)."); report("Use the sequence '-t .ub -r 11025 file.snd'"); report("If it came from a PC, it's probably a Soundtool file."); report("Use the sequence '-t .sndt file.snd'"); report("If it came from a NeXT, it's probably a .au file."); fail("Use the sequence '-t .au file.snd'\n"); } fail("File type '%s' of %s file is not known!", formp->filetype, formp->filename); } copyformat(ft, ft2) ft_t ft, ft2; { int noise = 0; if (ft2->info.rate == 0) { ft2->info.rate = ft->info.rate; noise = 1; } if (outformat.info.size == -1) { ft2->info.size = ft->info.size; noise = 1; } if (outformat.info.style == -1) { ft2->info.style = ft->info.style; noise = 1; } if (outformat.info.channels == -1) { ft2->info.channels = ft->info.channels; noise = 1; } if (outformat.comment == NULL) { ft2->comment = ft->comment; noise = 1; } return noise; } cmpformats(ft, ft2) ft_t ft, ft2; { int noise = 0; float abs; } /* check that all settings have been given */ checkformat(ft) ft_t ft; { if (ft->info.rate == 0) fail("Sampling rate for %s file was not given\n", ft->filename); if ((ft->info.rate < 100) || (ft->info.rate > 50000)) fail("Sampling rate %lu for %s file is bogus\n", ft->info.rate, ft->filename); if (ft->info.size == -1) fail("Data size was not given for %s file\nUse one of -b/-w/-l/-f/-d/-D", ft->filename); if (ft->info.style == -1) fail("Data style was not given for %s file\nUse one of -s/-u/-U/-A", ft->filename); /* it's so common, might as well default */ if (ft->info.channels == -1) ft->info.channels = 1; /* fail("Number of output channels was not given for %s file", ft->filename); */ } /* * If no effect given, decide what it should be. */ void checkeffect(effp) eff_t effp; { int already = (eff.name != (char *) 0); char *rate = 0, *chan = 0; int i; for (i = 0; effects[i].name; i++) { if (!chan && (effects[i].flags & EFF_CHAN)) chan = effects[i].name; if (! rate && (effects[i].flags & EFF_RATE)) rate = effects[i].name; } if (eff.name && ! writing) return; /* * Require mixdown for channel mismatch. * XXX Doesn't handle channel expansion. Need an effect for this. * Require one of the rate-changers on a rate change. * Override a rate change by explicitly giving 'copy' command. */ if (informat.info.channels != outformat.info.channels) { if (eff.name && !(eff.h->flags & EFF_CHAN)) fail("Need to do change number of channels first. Try the '%s' effect.", chan); if (! eff.name) { eff.name = chan; report( "Changing %d input channels to %d output channels with '%s' effect\n", informat.info.channels, outformat.info.channels, chan); geteffect(&eff); } } /* * Be liberal on rate difference errors. * Note that the SPARC 8000-8192 problem * comes in just under the wire. XXX * * Bogus. Should just do a percentage. */ if (abs(informat.info.rate - outformat.info.rate) > 200) { if (eff.name && !(eff.h->flags & EFF_RATE)) fail("Need to do rate change first. Try the '%s' effect.", rate); if (! eff.name) { eff.name = rate; report( "Changing sample rate %lu to rate %lu via noisy 'rate' effect\n", informat.info.rate, outformat.info.rate); geteffect(&eff); } } /* don't need to change anything */ if (! eff.name) eff.name = "copy"; if (! already) { geteffect(&eff); /* give default opts for manufactured effect */ (* eff.h->getopts)(&eff, 0, (char *) 0); } } /* * Check that we have a known effect name. */ void geteffect(effp) eff_t effp; { int i; for(i = 0; effects[i].name; i++) { char *s1 = effects[i].name, *s2 = effp->name; while(*s1 && *s2 && (tolower(*s1) == tolower(*s2))) s1++, s2++; if (*s1 || *s2) continue; /* not a match */ /* Found it! */ effp->h = &effects[i]; return; } /* Guido Van Rossum fix */ fprintf(stderr, "Known effects:"); for (i = 0; effects[i].name; i++) fprintf(stderr, "\t%s\n", effects[i].name); fail("\nEffect '%s' is not known!", effp->name); } /* Guido Van Rossum fix */ statistics() { if (dovolume && clipped > 0) report("Volume change clipped %d samples", clipped); } long volumechange(y) long y; { double y1; y1 = y * volume; if (y1 < -2147483647.0) { y1 = -2147483647.0; clipped++; } else if (y1 > 2147483647.0) { y1 = 2147483647.0; clipped++; } return y1; } #if defined(unix) || defined(AMIGA) filetype(fd) int fd; { struct stat st; fstat(fd, &st); return st.st_mode & S_IFMT; } #endif char *usagestr = "[ -V -S ] [ fopts ] ifile [ fopts ] ofile [ effect [ effopts ] ]\nfopts: -r rate -v volume -c channels -s/-u/-U/-A -b/-w/-l/-f/-d/-D -x\neffects and effopts: various"; usage(opt) char *opt; { #ifndef DOS /* single-threaded machines don't really need this */ fprintf(stderr, "%s: ", myname); #endif fprintf(stderr, "Usage: %s", usagestr); if (opt) fprintf(stderr, "\nFailed at: %s\n", opt); exit(1); } void #ifdef __STDC__ report(char *fmt, ...) #else report(va_alist) va_dcl #endif { va_list args; #ifndef __STDC__ char *fmt; #endif if (! verbose) return; #ifndef DOS /* single-threaded machines don't really need this */ fprintf(stderr, "%s: ", myname); #endif #ifdef __STDC__ va_start(args, fmt); #else va_start(args); fmt = va_arg(args, char *); #endif vfprintf(stderr, fmt, args); va_end(args); fprintf(stderr, "\n"); } void #ifdef __STDC__ fail(char *fmt, ...) #else fail(va_alist) va_dcl #endif { va_list args; #ifndef __STDC__ char *fmt; #endif #ifndef DOS /* single-threaded machines don't really need this */ fprintf(stderr, "%s: ", myname); #endif #ifdef __STDC__ va_start(args, fmt); #else va_start(args); fmt = va_arg(args, char *); #endif vfprintf(stderr, fmt, args); va_end(args); fprintf(stderr, "\n"); /* Close the input file and outputfile before exiting*/ ft = &informat; fclose(ft->fp); ft = &outformat; fclose(ft->fp); /* remove the output file because we failed */ REMOVE(ft->filename); exit(2); } strcmpcase(s1, s2) char *s1, *s2; { while(*s1 && *s2 && (tolower(*s1) == tolower(*s2))) s1++, s2++; return *s1 - *s2; }