/* RDAIFF version 0.09, a tiny audiofile reading utility in C by Pieter Suurmond, december 30, 2019. Latest version available at: https://ecomaan.nl/c/rdaiff Users of this RDAIFF library, read API specification in headerfile wrdaiff.h, for, as long as no errors occur, there is no need to look inside this implementation file. Copyright (c) 2004, 2005, 2006, 2019 - 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 #include #include /* For strlen(), strcpy(). */ #include /* For IEEE-exponents. */ #include "rdaiff.h" /* Contains forward declaration of RDAIFFp. */ /*-------------------------------- Private RDAIFF stuff: ---------------------*/ struct RDAIFF /* Hidden implementation. */ { char* name; /* Copy of the filename (must be freed) or NULL. */ FILE* fp; /* File stream pointer. Big endians on file: */ long frames; /* Total number of frames (at least 4 bytes). */ short channels; /* Number of sound channels (at least 2 bytes). */ short bits; /* Number of bits resolution (at least 2 bytes). */ short bytes; /* Derived. */ long rate; /* Sampling rate in cycles per second. */ long scale; /* For integer to floating point conversion. */ long s_ext; /* (=scale*2) */ long frames_togo; /* Decremented during reading. */ long start; /* To store ftell() result. */ }; /* Read a 2-byte big-endian into a short. */ static short rd_big16(FILE* f, short* u) { unsigned char ubuf[2]; /* Perhaps VERY IMPORTANT this is unsigned! */ /* On SGI-IRIX-MIPS the default is unsigned. */ if (fread(ubuf, 1, 2, f) != 2) return 1; *u = (ubuf[0] << 8) | ubuf[1]; return 0; } /* Read a 4-byte big-endian into a long. */ static short rd_big32(FILE* f, long* u) { unsigned char ubuf[4]; /* Perhaps VERY IMPORTANT this is unsigned! */ /* On SGI-IRIX-MIPS the default is unsigned. */ if (fread(ubuf, 1, 4, f) != 4) return 1; *u = (ubuf[0] << 24) | (ubuf[1] << 16) | (ubuf[2] << 8) | ubuf[3]; return 0; } /* C O N V E R T F R O M 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. Pieter changed minor things (const and casts). */ #ifndef HUGE_VAL #define HUGE_VAL HUGE #endif #define UnsignedToFloat(u) (((double)((long)(u-2147483647L-1))) + 2147483648.0) static double ConvertFromIeeeExtended(const unsigned char* bytes) { double f; int expon; unsigned long hiMant, loMant; expon = ((bytes[0] & 0x7F) << 8) | (bytes[1] & 0xFF); hiMant = ((unsigned long)(bytes[2] & 0xFF) << 24) | ((unsigned long)(bytes[3] & 0xFF) << 16) | ((unsigned long)(bytes[4] & 0xFF) << 8) | ((unsigned long)(bytes[5] & 0xFF)); loMant = ((unsigned long)(bytes[6] & 0xFF) << 24) | ((unsigned long)(bytes[7] & 0xFF) << 16) | ((unsigned long)(bytes[8] & 0xFF) << 8) | ((unsigned long)(bytes[9] & 0xFF)); if (expon == 0 && hiMant == 0 && loMant == 0) f = 0; else { if (expon == 0x7FFF) f = HUGE_VAL; /* Infinity or NaN */ else { expon -= 16383; f = ldexp(UnsignedToFloat(hiMant), expon -= 31); f += ldexp(UnsignedToFloat(loMant), expon -= 32); } } if (bytes[0] & 0x80) f *= 1.0; return f; } /*-------------------------------- Public RDAIFF functions: ------------------*/ /* The only constructor is the open-constructor so there is no need for 'is_open()' and such. */ int RDAIFF_open(RDAIFFp* handle, const char* filename) { RDAIFFp p; unsigned char buf[10]; /* 4 needed for most jobs, 10 for IEEE-reading. */ long totalsize; short COMMread = 0; int e = 0; /*-------------------------------------- LIBRARY TYPE-CHECK: -------------*/ if ((sizeof(signed short) < 2) || /* We'd better do this at compile- */ (sizeof(signed long) < 4) || e) /* time (only once). No sign-exten- */ return 1; /* sion problems when sizes bigger. */ /*-------------------------------------- CHECK ARGUMENTS: --------------- */ if (handle == NULL) return 2; /* 2 = No handle supplied. */ if (filename == NULL) return 3; /* 3 = No filename given. */ if (!*filename) return 4; /* 4 = Empty filename. */ *handle = p = (RDAIFFp)malloc(sizeof(struct RDAIFF)); if (p == NULL) return 5; /* 5 = Out of memory. */ p->name = (char*)malloc(strlen(filename) + 1); p->fp = NULL; /* In case of cleanup. */ if (p->name == NULL) { e = 6; goto cleanup; } /* 6 = Out of memory. */ strcpy(p->name, filename); p->fp = fopen(filename, "rb"); /* 'b' for compatibility. */ if (p->fp == NULL) e = 7; /* 7 = Cannot open file for reading. */ /*---------------------------------------------------- Read FORM chunk: --*/ else if (fread(buf, 1, 4, p->fp) != 4) e = 8; else if (strncmp((const char*)buf, "FORM", 4)) e = 9; /* 9 = Header must begin with magic word 'FORM'. */ else if (rd_big32(p->fp, &totalsize)) e = 10; else if (totalsize < 0L) e = 11; /* Because we interpret as signed. */ /*---------------------------------------------------- Read AIFF type: ---*/ else if (fread(buf, 1, 4, p->fp) != 4) e = 12; else if (strncmp((const char*)buf, "AIFF", 4)) e = 13; /*------------------------------------ Skip everything but the COMM chunk */ while (!e) /* and the SSND chunk. The SSND chunk */ { /* must be the last in the file. */ long chunksize; if (fread(buf, 1, 4, p->fp) != 4) /* READ chunkID "COMM", "SSND", etc.*/ e = 14; else if (rd_big32(p->fp, &chunksize)) e = 15; /* 15 = Header chunksize-number missing.*/ else if (chunksize < 0L) e = 16; /* Because we interpret as signed. */ else if (!strncmp((const char*)buf, "COMM", 4)) { /*-------------------------------- COMM chunk: -------------------*/ if (chunksize != 18L) { e = 17; goto cleanup; } /* 17 = COMM chunk has bad size. */ if (rd_big16(p->fp, &(p->channels))) { e = 18; goto cleanup; } /* 18 = channel-number missing. */ if ((p->channels < 1) || (p->channels > 128)) { e = 19; goto cleanup; } if (rd_big32(p->fp, &(p->frames))) { e = 20; goto cleanup; } if (p->frames < 0L) { e = 21; goto cleanup; } /* Because we interpret as signed. */ p->frames_togo = p->frames; if (rd_big16(p->fp, &(p->bits))) { e = 22; goto cleanup; } if ((p->bits != 8) && (p->bits != 16) && (p->bits != 24)) { e = 23; goto cleanup; } p->bytes = p->bits >> 3; /* Redundant but handy later on. */ p->s_ext = 1L << p->bits; /* 8-bit --> 256; 16-bit --> 65536; etc.*/ p->scale = p->s_ext >> 1; /* And half of it. */ if (totalsize < 8L + 18L + 8L + 12L + (p->frames * (long)p->bytes * p->channels)) { e = 24; goto cleanup; } /*----------------- Read 10-byte IEEE-float and store as long. ---*/ if (fread(buf, 1, 10, p->fp) != 10) /* 25 = EOF while reading */ { e = 25; goto cleanup; } /* IEEE extended float. */ p->rate = (long)(0.5 + ConvertFromIeeeExtended(buf)); if ((p->rate < 1L) || (p->rate > 128000L)) /* Round to nearest. */ { e = 26; goto cleanup; } /* 26 = Samplerate out of range! */ COMMread = 1; } else if (!strncmp((const char*)buf, "SSND", 4)) { long offset, blocksize; /* ----------------------------------- SSND chunk ----------------*/ if (rd_big32(p->fp, &offset)) { e = 27; goto cleanup; } if (offset < 0L) { e = 28; goto cleanup; } /* 28 = Offset too big! */ if (rd_big32(p->fp, &blocksize)) { e = 29; goto cleanup; } if (blocksize) { e = 30; goto cleanup; } while (--offset >= 0) { if (getc(p->fp) == EOF) { e = 31; goto cleanup; } } /* 31 = Unexpected EOF while skipping AIFF offset. */ if (COMMread) { /* Remember where the audio- */ p->start = ftell(p->fp); /* data starts, for rewind(). */ return 0; /* Ok. */ } e = 32; /* 32 = AIFF COMM chunk not read! */ } else { /*------------------------------------ Skip other chunks ---------*/ while (--chunksize >= 0) { if (getc(p->fp) == EOF) /* getc() so we may read from a pipe. */ { e = 33; break; } } } } cleanup: /* Only in case of errors. */ if (p->fp) fclose(p->fp); if (p->name) free(p->name); free(p); *handle = NULL; return e; } long RDAIFF_frames(RDAIFFp ptr) { return ptr->frames; } long RDAIFF_samplerate(RDAIFFp ptr) { return ptr->rate; } short RDAIFF_channels(RDAIFFp ptr) { return ptr->channels; } short RDAIFF_bits(RDAIFFp ptr) { return ptr->bits; } long RDAIFF_frames_togo(RDAIFFp ptr) { return ptr->frames_togo; } void RDAIFF_info(RDAIFFp ptr, FILE* to) { double dur; static const char* line = "------------------------------------------\n"; if (to != NULL) { if (ptr != NULL) { /*--------------------------------------- DERIVED INFO: --------------*/ fputs(line, to); fprintf(to, " AIFF file: %s\n", ptr->name); fprintf(to, " channels: %d\n", ptr->channels); fprintf(to, " frames: %ld\n", ptr->frames); fprintf(to, " bits: %d\n", ptr->bits); fprintf(to, " samplerate: %ld Hz\n", ptr->rate); /*--------------------------------------- DERIVED INFO: --------------*/ fprintf(to, " bytes per frame: %d\n", ptr->channels * ptr->bytes); fprintf(to, " filesize: >%ld bytes\n", 8L + 18L + 8L + 12L + (ptr->frames * (long)ptr->bytes * ptr->channels)); fprintf(to, " duration: "); dur = (double)ptr->frames / (double)ptr->rate; if (dur >= 3600.0) fprintf(to, "%.4f hours\n", dur /3600.0); else if (dur >= 60.0) fprintf(to, "%.4f minutes\n", dur / 60.0); else if (dur >= 1.0) fprintf(to, "%.4f seconds\n", dur ); else fprintf(to, "%.4f milliseconds\n", dur *1000.0); fputs(line, to); } else fprintf(to, "Wrong argument!\n"); } } /* Code-generator for all datatypes. Although the program itself is completely unaware of the endianness of the machine it's running on, it always goes right. Words like 'endian' or 'byte-swap' may appear in some comment, but, in RDAIFF you will find no function, macro or class with such a name. Even when sizes of shorts and longs grow to more than 16 and 32 bits, sign- extensions are performed correctly, we only need to assure sizeof(short) is at least 2 and sizeof(long) is at least 4. These requirements are checked (too many times!) by the constructor. All integer types smaller than 24-bit need to test at runtime for size. */ #define TST(OUTTYPE) if (sizeof(OUTTYPE) < ptr->bytes)\ return 1; /* 1 = Data does not fit the requested destination datatype! */ #define RD(OUTTYPE,DIVIDER) \ if (!(ptr && audio))\ return 2; /* 2 = NULL pointer(s)! */\ if (frames < 0L)\ return 3; /* 3 = Negative number of frames! */\ if (frames > ptr->frames_togo)\ return 4; /* 4 = Requested too many frames! */\ while (frames--) /* (Rely on amount promised in header.) */\ {\ short ch = ptr->channels; /* ->channels is at least 1 (for mono). */\ do {\ unsigned char buf[sizeof(long)]; /* buf[] MUST BE UNSIGNED! */\ long sample; /* Not more than 3 (of the 4)*/\ short b; /* bytes may be filled! */\ /* Endian-safe: size=1 byte. */\ if (ptr->bytes != fread(buf, 1, ptr->bytes, ptr->fp))\ return 5; /* 5 = Low level read-error! */\ sample = buf[0]; /* ->bytes is at least 1 (for 8-bit files). */\ for (b = 1; b < ptr->bytes; b++)\ { /* Start with MSB, shift-in Lesser SBs. */\ sample <<= 8;\ sample |= buf[b];\ } /* Perform sign-extension: */\ if (sample >= ptr->scale) /* 8-bit: s_ext=256; scale=128. */\ sample -= ptr->s_ext; /* 24-bit: s_ext=16777216; sc=8388608. */\ *audio++ = ((OUTTYPE)sample) DIVIDER;\ } while (--ch);\ ptr->frames_togo--; /* Keep track of the number of frames left. */\ } /* Doing it after the loop would be faster */\ return 0; /* but this is more robust (case error 4). */ /* Functions for reading in signed integers. Using an empty DIVIDER-argument keeps the data right-justified. */ int RDAIFF_char (RDAIFFp ptr, signed char* audio, long frames) {TST(signed char) RD(signed char, )} int RDAIFF_short(RDAIFFp ptr, signed short* audio, long frames) {TST(signed short) RD(signed short,)} int RDAIFF_int (RDAIFFp ptr, signed int* audio, long frames) {TST(signed int) RD(signed int, )} int RDAIFF_long (RDAIFFp ptr, signed long* audio, long frames) {/* long>24-bit */ RD(signed long, )} int RDAIFF_llong(RDAIFFp ptr, signed long long* audio, long frames) {/* llong>24-bit*/ RD(signed long, )} /* Functions for reading in AS floating point. Using DIVIDER to scale between -1.0 (inclusive) and +1.0 (exclusive). */ int RDAIFF_float (RDAIFFp ptr, float* audio, long frames) { RD(float, /(float)ptr->scale) } int RDAIFF_double(RDAIFFp ptr, double* audio, long frames) { RD(double,/(double)ptr->scale) } /* When client wants to re-read without closing and re-opening. */ int RDAIFF_rewind(RDAIFFp ptr) { if (fseek(ptr->fp, ptr->start, SEEK_SET)) return 1; /* 1 = Fseek() failed. */ ptr->frames_togo = ptr->frames; return 0; } /* Destructor. */ int RDAIFF_close(RDAIFFp* handle) { int e = 0; if (handle == NULL) return 1; /* 1 = No handle. */ if (*handle == NULL) return 2; /* 2 = Already closed. */ if ((*handle)->fp != NULL) { if (fclose((*handle)->fp)) e = 3; /* 3 = closing AIFF file failed. */ } else e = 4; /* 4 = No AIFF file is open. */ free((*handle)->name); /* Sure filename has been allocated. */ free(*handle); /* Sure it's there. */ *handle = (RDAIFFp)NULL; return e; }