/* WRAIFF version 0.24, december, 2019. A tiny audiofile writing utility in C. Latest version available at: https://ecomaan.nl/c/wraiff Copyright (c) 2004, 2005, 2006, 2009, 2014, 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 /* 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 implementation. */ { char* name; /* Internal copy of the filename. */ FILE* fp; /* File stream pointer. */ unsigned long frames; /* Total number of frames (>= 4 bytes). */ unsigned short channels; /* Number of sound channels (>= 2 bytes). */ unsigned short bits; /* Number of bits resolution (>= 2 bytes). */ unsigned short bytes; /* Derived. */ unsigned long rate; /* Sampling rate in cycles per second. */ double scale; /* For double to integer conversion. */ long long underflow; /* Values that may not be exceeded. */ long long overflow; unsigned long clipped; /* Number of samples clipped. */ }; #define kWRAIFF_MAX_FRAMES (1270080000L) /* Not more than 8 hours at CD- */ /* quality to protect harddisks */ /* and to prevent long-overflow. */ /* 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 #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; } /* Returns 0 on success, 10 on failure. */ static short wraiff_header(WRAIFFp A) { short e; unsigned long numBytes; char buf[10]; numBytes = A->frames * (unsigned long)A->bytes * (unsigned long)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+#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 (frames/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 writing header. */ return e; } /*-------------------------------- 8 public WRAIFF functions: ----------------*/ /* Constructor: */ int WRAIFF_open(WRAIFFp* handle, const char* filename, long samplerate, short channels, short bits) { int e = 0; WRAIFFp p; if ((sizeof(short) < 2) || /* Better do this at compile-time only. */ (sizeof(long) < 4) || /* No (sign-ext-)problems when bigger? */ (sizeof(long long) < 8) || (sizeof(float) < 4) || (sizeof(double) < 8) || e) /* Prevent const-comparison warning. */ return 1; /* 1 = Datatype too small! Sorry, you cannot use WRAIFF! */ if (handle == NULL) /* Check arguments. */ return 2; /* 2 = No handle supplied. */ *handle = NULL; if ((filename == NULL) || (!*filename)) return 3; /* 3 = No or empty filename. */ if ((samplerate < 1L) || (samplerate > 1000000000L)) /* 1 Hz to 1 GHz (inclusive). */ return 4; /* 4 = Samplerate out of range. */ if ((channels < 1) || (channels > 1000)) return 5; /* 5 = Too many/less channels. */ if ((bits < 1) || (bits > 56)) return 6; /* 6 = Wrong number of bits. */ *handle = p = (WRAIFFp)malloc(sizeof(struct WRAIFF)); if (p == NULL) return 7; /* 7 = Out of memory (1). */ p->name = (char*)malloc(strlen(filename) + 1); if (p->name == NULL) { e = 8; goto exit2; /* 8 = Out of memory (2). */ } strcpy(p->name, filename); p->fp = fopen(filename, "wb"); /* 'b' for compatibility. */ if (p->fp == NULL) { e = 9; goto exit1; /* 9 = Cannot create AIFF file. */ } p->rate = samplerate; /* Copy arguments. */ p->channels = channels; p->bits = bits; p->bytes = bits >> 3; if (p->bits & 7) /* Round up. */ p->bytes++; p->scale = pow(2.0, (double)(bits-1)); /* One less for sign. */ p->overflow = -1LL + (long long)(*handle)->scale; p->underflow = 0LL - (long long)(*handle)->scale; p->clipped = 0L; p->frames = kWRAIFF_MAX_FRAMES; /* Set to max size initially. */ e = wraiff_header(p); /* Write again at closing. */ p->frames = 0L; if (e) /* Error 10. */ { fclose(p->fp); exit1: free(p->name); exit2: free(p); *handle = NULL; } return e; } /* Code for writing signed chars, signed shorts and signed longs: */ #define WRITE_INT \ {\ long fr;\ long long a;\ short b, ch;\ char buff[8];\ int e = 0;\ \ if ((ptr == NULL) || (audio == NULL) || (frames < 0L)) /* Check args. */\ return 1;\ fr = frames;\ while (fr--)\ {\ ch = ptr->channels;\ while (ch--)\ {\ a = (long long)(*audio++);\ if (a < ptr->underflow)\ {\ a = ptr->underflow; /* Clip instead of sign-swap. */\ ptr->clipped++;\ e = 2; /* User may ignore errors 2 and 3 */\ } /* so let's count over/underflows. */\ else if (a > ptr->overflow)\ {\ a = ptr->overflow;\ ptr->clipped++;\ e = 3;\ }\ for (b = 0; b < ptr->bytes; b++) /* LIFO buffer. */\ {\ buff[b] = (char)a; /* 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((int)buff[b], ptr->fp))\ e = 4; /* Override error 2 and 3. */\ }\ }\ }\ ptr->frames += frames; /* Advance frame-counter. */\ if (ptr->frames > kWRAIFF_MAX_FRAMES) /* Care for your harddisk. */\ { if (!e) e = 5; } /* Don't override errors. */\ return e;\ } int WRAIFF_char (WRAIFFp ptr, const signed char* audio, long frames) WRITE_INT int WRAIFF_short (WRAIFFp ptr, const signed short* audio, long frames) WRITE_INT int WRAIFF_long (WRAIFFp ptr, const signed long* audio, long frames) WRITE_INT int WRAIFF_long_long(WRAIFFp ptr, const long long* audio, long frames) WRITE_INT /* Code for writing floats and doubles: */ #define WRITE_FP \ {\ long fr;\ long long a;\ short b, ch;\ char buff[8];\ int e = 0;\ \ if ((ptr == NULL) || (audio == NULL) || (frames < 0L)) /* Check args. */\ return 1;\ fr = frames;\ while (fr--)\ {\ ch = ptr->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 long)((ptr->scale * (*audio++)) - 0.5);\ if (a < ptr->underflow) /* -125.9 -> -126. */\ { /* -125.2 -> -125. */\ a = ptr->underflow; /* Clip instead of */\ ptr->clipped++; /* sign-swap. */\ e = 2; /* User may ignore errors 2 and 3 */\ } /* so let's count over/underflows. */\ }\ else\ {\ a = (long long)((ptr->scale * (*audio++)) + 0.5);\ if (a > ptr->overflow)\ {\ a = ptr->overflow;\ ptr->clipped++;\ e = 3;\ }\ }\ for (b = 0; b < ptr->bytes; b++) /* LIFO buffer. */\ {\ buff[b] = (char)a; /* 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((int)buff[b], ptr->fp))\ e = 4; /* Override error 2 and 3. */\ }\ }\ }\ ptr->frames += frames; /* Advance frame-counter. */\ if (ptr->frames > kWRAIFF_MAX_FRAMES) /* Care for your harddisk. */\ { if (!e) e = 5; } /* Don't override errors. */\ return e;\ } int WRAIFF_float (WRAIFFp ptr, const float* audio, long frames) WRITE_FP int WRAIFF_double(WRAIFFp ptr, const double* audio, long frames) WRITE_FP /* Print file statistics: */ int WRAIFF_info(WRAIFFp ptr, FILE* to) { double secs; const char* line = "--------------------------------------\n"; if (to != NULL) { if (ptr != NULL) { fputs(line, to); fprintf(to, " AIFF file: %s\n", ptr->name); fprintf(to, " channels: %u\n", ptr->channels); fprintf(to, " frames: %lu\n", ptr->frames); fprintf(to, " bits: %u\n", ptr->bits); fprintf(to, " samplerate: %lu Hz\n", ptr->rate); /*----------------------------------- DERIVED INFO: ------------*/ fprintf(to, " bytes/frame: %u\n", ptr->channels * ptr->bytes); fprintf(to, " filesize: %lu bytes\n", 8 + 18 + 8 + 12 + (8) + (ptr->frames * ptr->bytes * ptr->channels)); fprintf(to, " duration: "); secs = (double)ptr->frames / (double)ptr->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); /*----------------------------------- UNDER- AND OVERFLOWS: ----*/ if (ptr->clipped) { fprintf(to, " clipped: %ld sample", ptr->clipped); if (ptr->clipped > 1L) putc('s', to); /* Plural. */ fputs("!\n", to); } fputs(line, to); return 0; } fputs("Wrong argument(s)!\n", to); } return 1; } /* Some get-functions: */ const char* WRAIFF_get_filename(WRAIFFp ptr) { if (ptr == NULL) return NULL; /* Error! */ return ptr->name; } long WRAIFF_get_samplerate(WRAIFFp ptr) { if (ptr == NULL) return -1L; /* Error! */ return (long)ptr->rate; } long WRAIFF_get_num_frames(WRAIFFp ptr) { if (ptr == NULL) return -1L; /* Error! */ return (long)ptr->frames; } /* Destructor: */ int WRAIFF_close(WRAIFFp* handle) { int e = 0; if (handle == NULL) return 1; /* 1 means NULL-pointer supplied. */ if (*handle != NULL) /* Ok, already closed. */ { if (fseek((*handle)->fp, 0L, 0)) e = 2; /* 3 means cannot rewind. */ else e = wraiff_header(*handle); /* 10 means header-rewrite failure. */ if (fclose((*handle)->fp)) e += 100; /* >= 100 means closing AIFF failed. */ free((*handle)->name); /* Sure filename has been allocated. */ free(*handle); /* Sure it is there. */ *handle = (WRAIFFp)NULL; } return e; }