/* wraiff.c version 0.10, march 21, 2004. Tiny audiofile writing utility in C. Latest version available at http://www.hku.nl/~pieter/EDU/c/wraiff/ Users of this WRAIFF library, read API specification in headerfile wraiff.h, for, as long as no errors occur, there is no need to look inside this implementation file. Copyright (c) 2004 - Pieter Suurmond Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Any person wishing to distribute modifications to the Software is requested to send the modifications to the original developer so that they can be incorporated into the canonical version. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include /* For malloc() and free(). */ #include /* For FILE*, fprintf(), sprintf(), etc. */ #include /* For strcpy() and strlen(). */ #include #include "wraiff.h" /* Contains forward declaration of WRAIFFp. */ struct WRAIFF /* Hidden (softly protected) implementation. */ { char* name; /* Copy of the filename (must be freed) or NULL. */ FILE* fp; /* File stream pointer. Big endians on file: */ unsigned long frames; /* Total number of frames (at least 4 bytes). */ unsigned short channels; /* Number of sound channels (at least 2 bytes). */ unsigned short bits; /* Number of bits resolution (at least 2 bytes). */ unsigned short bytes; /* Derived. */ long rate; /* Sampling rate in cycles per second. */ double scale; /* For double to integer conversion. */ long underflow; /* Values that may not be exceeded. */ long overflow; unsigned long clipped; /* Number of clipped samples (errors 2 and 3). */ }; #define kWRAIFF_MAX_FRAMES (317520000L) /* Not more than 2 hours at CD- */ /* quality to protect harddisks. */ /* Writes 2 byte short, MSB first. Works ok on both big and little endians. */ static short wraiff_short_bin (unsigned short u0, FILE* fp) { if (EOF == putc(u0 >> 8, fp)) return 1; if (EOF == putc(u0 & 0xff, fp)) return 2; return 0; } /* Writes a 4 byte unsigned long, MSB first, to file. Both this and the above function are called by wraiff_header() a couple of times. */ static short wraiff_long_bin (unsigned long u0, FILE* fp) { unsigned long u1 = u0 >> 8; unsigned long u2 = u1 >> 8; if (EOF == putc((int)(u2 >> 8), fp)) return 1; /* Cast longs to ints. */ if (EOF == putc((int)(u2 & 0xff), fp)) return 2; if (EOF == putc((int)(u1 & 0xff), fp)) return 3; if (EOF == putc((int)(u0 & 0xff), fp)) return 4; return(0); } /* C O N V E R T T O I E E E E X T E N D E D Machine-independent I/O routines for IEEE floating-point numbers. NaN's and infinities are converted to HUGE_VAL or HUGE, which happens to be infinity on IEEE machines. Unfortunately, it is impossible to preserve NaN's in a machine-independent way. Infinities are, however, preserved on IEEE machines. These routines have been tested on the following machines: Apple Macintosh, MPW 3.1 C compiler Apple Macintosh, THINK C compiler Silicon Graphics IRIS, MIPS compiler Cray X/MP and Y/MP Digital Equipment VAX Implemented by Malcolm Slaney and Ken Turkowski. Malcolm Slaney contributions during 1988-1990 include big- and little- endian file I/O, conversion to and from Motorola's extended 80-bit floating-point format, and conversions to and from IEEE single- precision floating-point format. In 1991, Ken Turkowski implemented the conversions to and from IEEE double-precision format, added more precision to the extended conversions, and accommodated conversions involving +/- infinity, NaN's, and denormalized numbers. */ #ifndef HUGE_VAL # define HUGE_VAL HUGE #endif /*HUGE_VAL*/ # define FloatToUnsigned(f) ((unsigned long)(((long)(f - 2147483648.0)) + 2147483647L) + 1) static void ConvertToIeeeExtended(double num, char *bytes) { int sign, expon; double fMant, fsMant; unsigned long hiMant, loMant; if (num < 0) { sign = 0x8000; num *= -1; } else { sign = 0; } if (num == 0) { expon = 0; hiMant = 0; loMant = 0; } else { fMant = frexp(num, &expon); if ((expon > 16384) || !(fMant < 1)) { /* Infinity or NaN */ expon = sign|0x7FFF; hiMant = 0; loMant = 0; /* infinity */ } else { /* Finite */ expon += 16382; if (expon < 0) { /* denormalized */ fMant = ldexp(fMant, expon); expon = 0; } expon |= sign; fMant = ldexp(fMant, 32); fsMant = floor(fMant); hiMant = FloatToUnsigned(fsMant); fMant = ldexp(fMant - fsMant, 32); fsMant = floor(fMant); loMant = FloatToUnsigned(fsMant); } } bytes[0] = expon >> 8; bytes[1] = expon; bytes[2] = (char)(hiMant >> 24); /* Cast to chars to prevent warnings. */ bytes[3] = (char)(hiMant >> 16); bytes[4] = (char)(hiMant >> 8); bytes[5] = (char)hiMant; bytes[6] = (char)(loMant >> 24); bytes[7] = (char)(loMant >> 16); bytes[8] = (char)(loMant >> 8); bytes[9] = (char)loMant; } /* Only used within this file, no need to call it directly. Call open_wraiff() and close_wraiff() instead. Returns 10 on failure. */ static short wraiff_header (WRAIFFp A) { short e; unsigned long numBytes; char buf[10]; numBytes = A->frames * (unsigned long)A->bytes * A->channels; e = (EOF == fputs("FORM", A->fp)); /* Write IFF header. */ e |= wraiff_long_bin(8 + 18 + 8 + 12 + numBytes, A->fp); /* COMM hdr + COMM chunk + */ /* SSND hdr + SSND chunk + numBytes. */ e |= (EOF == fputs("AIFF", A->fp)); /* File type */ /* COMM chunk -----------------------*/ e |= (EOF == fputs("COMM", A->fp)); /* Describes encoding and #frames. */ e |= wraiff_long_bin ((long)18, A->fp); /* COMM chunk size. */ e |= wraiff_short_bin(A->channels, A->fp); /* Number of channels. */ e |= wraiff_long_bin(A->frames, A->fp); /* Number of sampleframes. */ e |= wraiff_short_bin(A->bits, A->fp); /* Sample width, in bits. */ ConvertToIeeeExtended((double)A->rate, buf); e |= (-10 + fwrite(buf, 1, 10, A->fp)); /* Samplerate in cycles per second. */ /* SSND chunk -----------------------*/ e |= (EOF == fputs("SSND", A->fp)); e |= wraiff_long_bin(8 + numBytes, A->fp); /* Chunk size. */ e |= wraiff_long_bin((long)0L, A->fp); /* Offset. */ e |= wraiff_long_bin((long)0L, A->fp); /* Block size. */ if (e) e = 10; /* 10 = Error (re)writing header. */ return e; } int open_wraiff (WRAIFFp* A, const char* filename, long samplerate, short channels, short bits) { int err; /* Check arguments. */ if (!A) return 1; /* 1 = No handle supplied. */ if (!filename) return 2; /* 2 = No filename. */ if (!*filename) return 3; /* 3 = Empty filename. */ if ((samplerate < 1L) || (samplerate > 128000L)) return 4; /* 4 = Samplerate out of range. */ if ((channels < 1) || (channels > 128)) return 5; /* 5 = Too many or too less channels. */ if ((bits & 7) || (bits < 8) || (bits > 64)) return 6; /* 6 = Wrong number of bits. */ *A = (WRAIFFp)malloc(sizeof(struct WRAIFF)); if (!*A) return 7; /* 7 = Out of memory. */ (*A)->name = malloc(strlen(filename) + 1); if (!(*A)->name) { free(*A); *A = NULL; return 7; } /* 7 = Out of memory. */ strcpy((*A)->name, filename); (*A)->fp = fopen(filename, "wb"); /* 'b' for platform-compatibility. */ if (!(*A)->fp) { free((*A)->name); free(*A); *A = NULL; return 8; /* 8 = Cannot create AIFF file. */ } (*A)->rate = samplerate; /* Copy arguments. */ (*A)->channels = channels; (*A)->bits = bits; (*A)->bytes = bits >> 3; /* Redundant. */ (*A)->scale = pow(2.0, (double)(bits - 1)); /* One bit less for sign. */ (*A)->overflow = -1L + (long)(*A)->scale; (*A)->underflow = 0L - (long)(*A)->scale; (*A)->clipped = 0L; (*A)->frames = kWRAIFF_MAX_FRAMES; /* Set to max size initially. */ err = wraiff_header(*A); /* Will write again at closing. */ (*A)->frames = 0L; if (err) /* Error 10. */ { fclose((*A)->fp); free((*A)->name); free(*A); *A = NULL; } return err; } int write_wraiff (WRAIFFp* A, double* audio, long frames) { long fr, a; /* Long should at least be 32 bit. */ short b, ch; int buff[8]; if (!(A && *A && audio && frames)) return 1; fr = frames; while (fr--) { ch = (*A)->channels; while (ch--) /* Correct rounding of bipolar signals. */ { /* For example, at 8 bits: -125.4 -> -125. */ if (*audio < 0.0) /* -125.8 -> -126. */ { a = (long)(((*A)->scale * (*audio++)) - 0.5); if (a < (*A)->underflow) /* +125.9 -> +126. */ { /* +125.2 -> +125. */ a = (*A)->underflow; /* Let's clip instead of */ (*A)->clipped++; /* drastic sign-swap. */ return 2; /* User may ignore errors 2 and 3 */ } /* so let's count over/underflows. */ } else { a = (long)(((*A)->scale * (*audio++)) + 0.5); if (a > (*A)->overflow) { a = (*A)->overflow; (*A)->clipped++; return 3; } } for (b = 0; b < (*A)->bytes; b++) /* LIFO buffer. */ { buff[b] = a & 0xff; /* On little endians, these two */ a >>= 8; /* b-loops do the byte-swapping. */ } /* On big endian processors, no- */ while (b--) /* thing effectively happens here. */ { if (EOF == putc(buff[b], (*A)->fp)) return 4; } } } (*A)->frames += frames; /* Advance frame-counter. */ if ((*A)->frames > kWRAIFF_MAX_FRAMES) /* Care for your harddisk. */ return 5; return 0; } void print_wraiff (WRAIFFp* A, FILE* to) { double secs; const char* line = "--------------------------------------\n"; if (A && *A && to) { fprintf(to, line); fprintf(to, " AIFF file: %s\n", (*A)->name); fprintf(to, " channels: %d\n", (*A)->channels); fprintf(to, " frames: %ld\n", (*A)->frames); fprintf(to, " bits: %d\n", (*A)->bits); fprintf(to, " samplerate: %ld Hz\n", (*A)->rate); /*----------------------------------- DERIVED INFO: -------------*/ fprintf(to, " bytes/frame: %d\n", (*A)->channels * (*A)->bytes); fprintf(to, " filesize: %ld bytes\n", 8 + 18 + 8 + 12 + (8) + ((*A)->frames * (*A)->bytes * (*A)->channels)); fprintf(to, " duration: "); secs = (double)(*A)->frames / (double)(*A)->rate; if (secs >= 3600.0) fprintf(to, "%.2f hours\n", secs/3600.0); if (secs >= 60.0) fprintf(to, "%.2f minutes\n", secs/60.0); else fprintf(to, "%.2f seconds\n", secs); /*----------------------------------- UNDERFLOWS+OVERFLOWS: -----*/ if ((*A)->clipped) { fprintf(to, " clipped: %ld sample", (*A)->clipped); if ((*A)->clipped > 1L) fprintf(to, "s"); /* Plural. */ fprintf(to, "!\n"); } fprintf(to, line); } else if (to) fprintf(to, "Wrong argument!\n"); } int close_wraiff (WRAIFFp* A) { short err = 0; if (!A) return 1; /* 1 = No handle. */ if (!*A) return 2; /* 2 = Already closed. */ if (fseek((*A)->fp, 0L, 0)) err = 3; /* 3 = Can't rewind to rewrite hdr. */ else err = wraiff_header(*A); /* 10. */ if (fclose((*A)->fp)) err += 100; /* >100 = closing AIFF file failed. */ free((*A)->name); /* Sure filename has been allocated. */ free(*A); /* Sure it's there. */ *A = (WRAIFFp)NULL; return err; }