/*************************************************************************** * Copyright (C) 1994 Charles P. Peterson * * 4007 Enchanted Sun, San Antonio, Texas 78244-1254 * * Email: Charles_P_Peterson@fcircus.sat.tx.us * * * * This is free software with NO WARRANTY. * * See gfft.c, or run program itself, for details. * * Support is available for a fee. * *************************************************************************** * * Program: gfft--General FFT analysis * File: format.c * Purpose: parse formatted files * Author: Charles Peterson (CPP) * History: 18-October-1993 CPP; Created. * Comment: Thanks to Guido van Rossum for SOX and David Champion * for OmniPlay, from which much was learned. * Thanks to Malcolm Slaney and Ken Turkowski for * ConvertFromIeeeExtended, from which much was learned. * However, this is an original implementation, containing * no previously copyrighted code. */ #include #include #include "gfft.h" #include "settings.h" #include "format.h" #define NO_ERROR FALSE #define CORRUPT TRUE #define UNAVAIL_COMPRESSION 2 static unsigned long offset = 0; /* Running offset pointer used here */ /* * Static function declarations */ static void read_form_format (void); /* Includes 8SVX, AIFF, AIFC types */ static void read_8svx_format (long form_cksize); static int read_vhdr (ULONG form_cksize, struct ChunkHeader chunk_h); static void read_aiff_format (long form_cksize); static int read_comm_aiff (ULONG form_cksize, struct ChunkHeader chunk_h); static void read_aifc_format (long form_cksize); static int read_comm_aifc (ULONG form_cksize, struct ChunkHeader chunk_h); static double double_from_extended (UBYTE *extended); static void read_avr_format (void); void reset_format (void) { /* * Reset Global Format Info */ FileFormat = UNFORMATTED; FileFormatOK = FALSE; FileRate = AUTO_RATE; FileDataOffset = 0; FileFrames = 0; FileVolume = 1.0L; FileOctaves = 1; FileChannels = 1; FileOneShotHiFrames = 0; FileRepeatHiFrames = 0; } void read_format (void) { ID format_id; int format = UNFORMATTED; reset_format(); if (fread (&format_id, sizeof format_id, 1, ReadPtr)) { offset = sizeof format_id; /* This is where offset count begins */ switch (format_id) { case ID_FORM: /* IFF 8SVX, AIFF, and AIFC */ format = ID_FORM; /* Clarified later */ break; case ID_RIFF: format = ID_RIFF; break; case ID_VOCH: format = ID_VOCH; break; case ID_AVR: format = ID_AVR; break; } } if (format != UNFORMATTED) { if (IgnoreFormat) { error_message (FILE_FORMAT_IGNORED); return; } Rate = AUTO_RATE; /* override must be forced afterwards */ } FileFormat = format; switch (format) { case ID_FORM: read_form_format (); break; case ID_AVR: read_avr_format (); break; case UNFORMATTED: break; default: error_message (UNSUPPORTED_FORMAT); FileFormatOK = FALSE; } } static void read_form_format (void) /* Includes 8SVX, AIFF, AIFC types */ { ULONG form_cksize; ID form_type; if (fread (&form_cksize, sizeof form_cksize, 1, ReadPtr) && fread (&form_type, sizeof form_type, 1, ReadPtr)) { offset += sizeof form_cksize + sizeof form_type; switch (form_type) { case ID_8SVX: FileFormat = ID_8SVX; read_8svx_format (form_cksize); break; case ID_AIFF: FileFormat = ID_AIFF; read_aiff_format (form_cksize); break; case ID_AIFC: FileFormat = ID_AIFC; read_aifc_format (form_cksize); break; default: error_message (UNSUPPORTED_FORMAT); break; } } else { error_message (CORRUPT_IFF); /* Missing cksize and/or type! */ } } static void read_8svx_format (long form_cksize) /* * Chunks are allowed to be in any order. * But, a fast and lazy approach is taken to validation: * Chunks looked for: VHDR and BODY * (Once those two have been found, and VHDR read, I exit.) * BODY chunk isn't read (so premature file end must be detected later) * BODY chunk is fseek'd over only if VHDR hasn't been found yet. * form_cksize isn't actually checked */ { int error = NO_ERROR; struct ChunkHeader chunk_h; ULONG skip_bytes; BOOLEAN vhdr_found = FALSE; BOOLEAN body_found = FALSE; InputFormat.bits = 8; /* True of all 8SVX */ InputFormat.zero = 0; /* Ditto */ while (fread (&chunk_h, sizeof chunk_h, 1, ReadPtr)) { offset += sizeof chunk_h; switch (chunk_h.ckID) { case ID_VHDR: vhdr_found = TRUE; error = read_vhdr (form_cksize, chunk_h); offset += WordAlign (chunk_h.ckSize); break; case ID_BODY: body_found = TRUE; FileDataOffset = offset; if (!vhdr_found && !error) /* If still looking for VHDR */ { skip_bytes = WordAlign (chunk_h.ckSize); error = !!fseek (ReadPtr, skip_bytes, SEEK_CUR); offset += skip_bytes; } break; default: skip_bytes = WordAlign (chunk_h.ckSize); error = !!fseek (ReadPtr, skip_bytes, SEEK_CUR); offset += skip_bytes; break; } if (error || (vhdr_found && body_found)) { break; } } if (!vhdr_found || !body_found || error) { if (error <= CORRUPT) /* Higher errors reported elsewhere */ { error_message (CORRUPT_IFF); } } else { FileFormatOK = TRUE; } } static int read_vhdr (ULONG form_cksize, struct ChunkHeader chunk_h) { int error = CORRUPT; /* defaulted if chunk not completely read */ struct VHDR vhdr; ULONG skip_bytes; if (chunk_h.ckSize < sizeof vhdr) { return CORRUPT; /* Error! Pre '85 or corrupt! */ } if (fread (&vhdr, sizeof vhdr, 1, ReadPtr)) { FileRate = vhdr.samplesPerSec; if (vhdr.sCompression) { error_message (COMPRESSION_NOT_SUPPORTED); return UNAVAIL_COMPRESSION; } FileVolume = vhdr.volume / 65536.0L; FileOctaves = vhdr.ctOctave; FileOneShotHiFrames = vhdr.oneShotHiSamples; FileRepeatHiFrames = vhdr.repeatHiSamples; FileFrames = FileOneShotHiFrames + FileRepeatHiFrames; error = NO_ERROR; if (skip_bytes = WordAlign (chunk_h.ckSize - sizeof vhdr)) /* = */ { error = !!fseek (ReadPtr, skip_bytes, SEEK_CUR); offset += skip_bytes; } } return error; } static void read_aiff_format (long form_cksize) /* * Chunks are allowed to be in any order. * But, a fast and lazy approach is taken to validation: * Chunks looked for: Common (COMM) and Sound Data (SSND) * (Note: COMM here is different from the AIFC version.) * (Once those two have been found, and COMM read, I exit.) * SSND chunk isn't read (so premature file end must be detected later) * SSND chunk is fseek'd over only if VHDR hasn't been found yet. * form_cksize isn't actually checked */ { ULONG skip_bytes; int error = NO_ERROR; struct ChunkHeader chunk_h; struct SoundDataChunkInfo sdci; BOOLEAN ssnd_found = FALSE; BOOLEAN comm_found = FALSE; while (fread (&chunk_h, sizeof chunk_h, 1, ReadPtr)) { offset += sizeof chunk_h; switch (chunk_h.ckID) { case ID_COMM: comm_found = TRUE; error = read_comm_aiff (form_cksize, chunk_h); offset += WordAlign (chunk_h.ckSize); break; case ID_SSND: ssnd_found = TRUE; error = !fread (&sdci, sizeof sdci, 1, ReadPtr); offset += sizeof sdci; FileDataOffset = offset + sdci.offset; if (!comm_found && !error) { skip_bytes = WordAlign (chunk_h.ckSize - sizeof sdci); error = !!fseek (ReadPtr, skip_bytes, SEEK_CUR); offset += skip_bytes; } break; default: skip_bytes = WordAlign (chunk_h.ckSize); error = !!fseek (ReadPtr, skip_bytes, SEEK_CUR); offset += skip_bytes; break; } if (error || (comm_found && ssnd_found)) { break; } } if (!comm_found || !ssnd_found || error) { if (error <= CORRUPT) /* Higher errors reported elsewhere */ { error_message (CORRUPT_AIFF); } } else { FileFormatOK = TRUE; } } static int read_comm_aiff (ULONG form_cksize, struct ChunkHeader chunk_h) { int error = CORRUPT; /* defaulted if chunk not completely read */ int skip_bytes; struct CommAiff comm; if (chunk_h.ckSize < sizeof comm) { return CORRUPT; /* Error...corrupt or unsupported AIFF format */ } if (fread (&comm, sizeof comm, 1, ReadPtr)) { FileChannels = comm.numChannels; FileFrames = comm.numSampleFrames; InputFormat.bits = comm.sampleSize; InputFormat.zero = 0; FileRate = double_from_extended (comm.sampleRate); error = NO_ERROR; if (skip_bytes = WordAlign (chunk_h.ckSize - sizeof comm)) /* = */ { error = !!fseek (ReadPtr, skip_bytes, SEEK_CUR); offset += skip_bytes; } } return error; } static void read_aifc_format (long form_cksize) /* * Chunks are allowed to be in any order. * But, a fast and lazy approach is taken to validation: * Chunks looked for: COMM and SSND * (Once those two have been found, and COMM read, I exit.) * SSND chunk isn't read (so premature file end must be detected later) * SSND chunk is fseek'd over only if VHDR hasn't been found yet. * form_cksize isn't actually checked */ { ULONG skip_bytes; int error = NO_ERROR; struct ChunkHeader chunk_h; struct SoundDataChunkInfo sdci; BOOLEAN ssnd_found = FALSE; BOOLEAN comm_found = FALSE; while (fread (&chunk_h, sizeof chunk_h, 1, ReadPtr)) { offset += sizeof chunk_h; switch (chunk_h.ckID) { case ID_COMM: comm_found = TRUE; error = read_comm_aifc (form_cksize, chunk_h); offset += WordAlign (chunk_h.ckSize); break; case ID_SSND: ssnd_found = TRUE; error = !fread (&sdci, sizeof sdci, 1, ReadPtr); offset += sizeof sdci; FileDataOffset = offset + sdci.offset; if (!comm_found && !error) { skip_bytes = WordAlign (chunk_h.ckSize - sizeof sdci); error = !!fseek (ReadPtr, skip_bytes, SEEK_CUR); offset += skip_bytes; } break; default: skip_bytes = WordAlign (chunk_h.ckSize); error = !!fseek (ReadPtr, skip_bytes, SEEK_CUR); offset += skip_bytes; break; } if (error || (comm_found && ssnd_found)) { break; } } if (!comm_found || !ssnd_found || error) { if (error <= CORRUPT) { error_message (CORRUPT_AIFC); } } else { FileFormatOK = TRUE; } } static int read_comm_aifc (ULONG form_cksize, struct ChunkHeader chunk_h) { int error = CORRUPT; /* Defaulted if chunk not read */ int skip_bytes; struct CommAifc comm; if (chunk_h.ckSize < sizeof comm) { return CORRUPT; /* Error...corrupt or unsupported AIFF format */ } if (fread (&comm, sizeof comm, 1, ReadPtr)) { FileChannels = comm.numChannels; FileFrames = comm.numSampleFrames; InputFormat.bits = comm.sampleSize; InputFormat.zero = 0; FileRate = double_from_extended (comm.sampleRate); if (comm.compressionType != ID_NONE) { error_message (COMPRESSION_NOT_SUPPORTED); return UNAVAIL_COMPRESSION; } error = NO_ERROR; if (skip_bytes = WordAlign (chunk_h.ckSize - sizeof comm)) /* = */ { error = !!fseek (ReadPtr, skip_bytes, SEEK_CUR); offset += skip_bytes; } } return error; } /* * The following is based on ideas from ConvertFromIeeeExtended * by Malcolm Slaney and Ken Turkowski. However, since that was * copyright by Apple Computer, Inc., I have prepared an original * implementation here. * * Note: NaN's and infinities are converted to HUGE_VAL. This is * not very nice for NaN's. * * WARNING! THIS HASN'T BEEN WELL TESTED, SO I WOULDN'T USE THIS FOR * OTHER OR SERIOUS PURPOSES OR ON OTHER ARCHITECTURES. */ #define UPPER_EXP_MASK 0x7F #define SIGN_MASK 0x80 #define INFINITE_EXPONENT 0x7FFF #define HI_E_OFF 16414 /* (16383 + 31) */ #define LO_E_OFF 16446 /* (16383 + 31 + 32) */ #define U_TO_F(u) (((double) ((long) ((u)-2147483647L-1))) + 2147483648.0) static double double_from_extended (UBYTE *extended) { double dval; ULONG high_mantissa; ULONG low_mantissa; int exponent; exponent = ((extended[0] & UPPER_EXP_MASK) << 8) | extended[1]; high_mantissa = ((ULONG) (extended[2] << 24)) | ((ULONG) (extended[3] << 16)) | ((ULONG) (extended[4] << 8)) | (ULONG) extended[5]; low_mantissa = ((ULONG) (extended[6] << 24)) | ((ULONG) (extended[7] << 16)) | ((ULONG) (extended[8] << 8)) | (ULONG) extended[9]; if (exponent == 0 && high_mantissa == 0 && low_mantissa == 0) { dval = 0; } else { if (exponent == INFINITE_EXPONENT) /* Infinity or NaN */ { dval = HUGE_VAL; /* from ANSI C math.h */ } else { dval = ldexp ( U_TO_F (high_mantissa), exponent - HI_E_OFF) + ldexp ( U_TO_F (low_mantissa), exponent - LO_E_OFF); } if (extended[0] & SIGN_MASK) { dval = (-dval); } } return dval; } static void read_avr_format (void) { struct AVRH avrh; if (!fread (&avrh, sizeof avrh, 1, ReadPtr)) { error_message (CORRUPT_AVR); } else { FileFormatOK = TRUE; FileDataOffset = sizeof (ID) + sizeof (struct AVRH); if (avrh.mono == 0) { FileChannels = 1; } else if (avrh.mono == -1) { FileChannels = 2; } else { error_message (CORRUPT_AVR); FileFormatOK = FALSE; } InputFormat.bits = avrh.rez; if (InputFormat.bits > 16 || InputFormat.bits < 0) { error_message (CORRUPT_AVR); FileFormatOK = FALSE; } if (avrh.sign == -1) { InputFormat.zero = 0; } else if (avrh.sign == 0) { int sample_width = (InputFormat.bits > 8) ? 16 : 8; InputFormat.zero = ((unsigned long) 0xffffffff >> (33 - sample_width)) + (unsigned long) 1; } else { error_message (CORRUPT_AVR); FileFormatOK = FALSE; } FileRate = avrh.rate & 0xFFFFFF; /* I don't understand why */ /* * Maybe FF in upper bits MEANS number is an integer, no FF means float * But, my docs didn't say this */ FileFrames = avrh.size; if (FileFrames < 0) { error_message (CORRUPT_AVR); FileFormatOK = FALSE; } if (avrh.res2) { error_message (COMPRESSION_NOT_SUPPORTED); FileFormatOK = FALSE; } } }