Metropoli BBS
VIEWER: format.c MODE: TEXT (ASCII)
/***************************************************************************
 *		  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 <math.h>
#include <stdio.h>
#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;
	}
    }
}
[ RETURN TO DIRECTORY ]