/*----------------------------------------------------------------------------
 *
 * File:
 * eas_wave.c
 *
 * Contents and purpose:
 * This module contains .WAV file functions for the EAS synthesizer
 * test harness.
 *
 * Copyright Sonic Network Inc. 2005

 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *----------------------------------------------------------------------------
 * Revision Control:
 *   $Revision: 658 $
 *   $Date: 2007-04-24 13:35:49 -0700 (Tue, 24 Apr 2007) $
 *----------------------------------------------------------------------------
*/

/* lint complaints about most C library headers, so we use our own during lint step */
#ifdef _lint
#include "lint_stdlib.h"
#else
#include <stdio.h>
#include <stdlib.h>
#endif

#include "eas_wave.h"

/* .WAV file format tags */
const EAS_U32 riffTag = 0x46464952;
const EAS_U32 waveTag = 0x45564157;
const EAS_U32 fmtTag = 0x20746d66;
const EAS_U32 dataTag = 0x61746164;

#ifdef _BIG_ENDIAN
/*----------------------------------------------------------------------------
 * FlipDWord()
 *----------------------------------------------------------------------------
 * Purpose: Endian flip a DWORD for big-endian processors
 *
 * Inputs:
 *
 * Outputs:
 *
 *----------------------------------------------------------------------------
*/
static void FlipDWord (EAS_U32 *pValue)
{
    EAS_U8 *p;
    EAS_U32 temp;

    p = (EAS_U8*) pValue;
    temp = (((((p[3] << 8) | p[2]) << 8) | p[1]) << 8) | p[0];
    *pValue = temp;
}

/*----------------------------------------------------------------------------
 * FlipWord()
 *----------------------------------------------------------------------------
 * Purpose: Endian flip a WORD for big-endian processors
 *
 * Inputs:
 *
 * Outputs:
 *
 *----------------------------------------------------------------------------
*/
static void FlipWord (EAS_U16 *pValue)
{
    EAS_U8 *p;
    EAS_U16 temp;

    p = (EAS_U8*) pValue;
    temp = (p[1] << 8) | p[0];
    *pValue = temp;
}

/*----------------------------------------------------------------------------
 * FlipWaveHeader()
 *----------------------------------------------------------------------------
 * Purpose: Endian flip the wave header for big-endian processors
 *
 * Inputs:
 *
 * Outputs:
 *
 *----------------------------------------------------------------------------
*/
static void FlipWaveHeader (WAVE_HEADER *p)
{

    FlipDWord(&p->nRiffTag);
    FlipDWord(&p->nRiffSize);
    FlipDWord(&p->nWaveTag);
    FlipDWord(&p->nFmtTag);
    FlipDWord(&p->nFmtSize);
    FlipDWord(&p->nDataTag);
    FlipDWord(&p->nDataSize);
    FlipWord(&p->fc.wFormatTag);
    FlipWord(&p->fc.nChannels);
    FlipDWord(&p->fc.nSamplesPerSec);
    FlipDWord(&p->fc.nAvgBytesPerSec);
    FlipWord(&p->fc.nBlockAlign);
    FlipWord(&p->fc.wBitsPerSample);

}
#endif

/*----------------------------------------------------------------------------
 * WaveFileCreate()
 *----------------------------------------------------------------------------
 * Purpose: Opens a wave file for writing and writes the header
 *
 * Inputs:
 *
 * Outputs:
 *
 *----------------------------------------------------------------------------
*/

WAVE_FILE *WaveFileCreate (const char *filename, EAS_I32 nChannels, EAS_I32 nSamplesPerSec, EAS_I32 wBitsPerSample)
{
    WAVE_FILE *wFile;

    /* allocate memory */
    wFile = malloc(sizeof(WAVE_FILE));
    if (!wFile)
        return NULL;
    wFile->write = EAS_TRUE;

    /* create the file */
    wFile->file = fopen(filename,"wb");
    if (!wFile->file)
    {
        free(wFile);
        return NULL;
    }

    /* initialize PCM format .WAV file header */
    wFile->wh.nRiffTag = riffTag;
    wFile->wh.nRiffSize = sizeof(WAVE_HEADER) - 8;
    wFile->wh.nWaveTag = waveTag;
    wFile->wh.nFmtTag = fmtTag;
    wFile->wh.nFmtSize = sizeof(FMT_CHUNK);

    /* initalize 'fmt' chunk */
    wFile->wh.fc.wFormatTag = 1;
    wFile->wh.fc.nChannels = (EAS_U16) nChannels;
    wFile->wh.fc.nSamplesPerSec = (EAS_U32) nSamplesPerSec;
    wFile->wh.fc.wBitsPerSample = (EAS_U16) wBitsPerSample;
    wFile->wh.fc.nBlockAlign = (EAS_U16) (nChannels * (EAS_U16) (wBitsPerSample / 8));
    wFile->wh.fc.nAvgBytesPerSec = wFile->wh.fc.nBlockAlign * (EAS_U32) nSamplesPerSec;

    /* initialize 'data' chunk */
    wFile->wh.nDataTag = dataTag;
    wFile->wh.nDataSize = 0;

#ifdef _BIG_ENDIAN
    FlipWaveHeader(&wFile->wh);
#endif

    /* write the header */
    if (fwrite(&wFile->wh, sizeof(WAVE_HEADER), 1, wFile->file) != 1)
    {
        fclose(wFile->file);
        free(wFile);
        return NULL;
    }

#ifdef _BIG_ENDIAN
    FlipWaveHeader(&wFile->wh);
#endif

    /* return the file handle */
    return wFile;
} /* end WaveFileCreate */

/*----------------------------------------------------------------------------
 * WaveFileWrite()
 *----------------------------------------------------------------------------
 * Purpose: Writes data to the wave file
 *
 * Inputs:
 *
 * Outputs:
 *
 *----------------------------------------------------------------------------
*/
EAS_I32 WaveFileWrite (WAVE_FILE *wFile, void *buffer, EAS_I32 n)
{
    EAS_I32 count;

    /* make sure we have an open file */
    if (wFile == NULL)
    {
        return 0;
    }

#ifdef _BIG_ENDIAN
    {
        EAS_I32 i;
        EAS_U16 *p;
        p = buffer;
        i = n >> 1;
        while (i--)
            FlipWord(p++);
    }
#endif

    /* write the data */
    count = (EAS_I32) fwrite(buffer, 1, (size_t) n, wFile->file);

    /* add the number of bytes written */
    wFile->wh.nRiffSize += (EAS_U32) count;
    wFile->wh.nDataSize += (EAS_U32) count;

    /* return the count of bytes written */
    return count;
} /* end WriteWaveHeader */

/*----------------------------------------------------------------------------
 * WaveFileClose()
 *----------------------------------------------------------------------------
 * Purpose: Opens a wave file for writing and writes the header
 *
 * Inputs:
 *
 * Outputs:
 *
 *----------------------------------------------------------------------------
*/

EAS_BOOL WaveFileClose (WAVE_FILE *wFile)
{
    EAS_I32 count = 1;

    /* return to beginning of file and write the header */
    if (wFile->write)
    {
        if (fseek(wFile->file, 0L, SEEK_SET) == 0)
        {

#ifdef _BIG_ENDIAN
            FlipWaveHeader(&wFile->wh);
#endif
            count = (EAS_I32) fwrite(&wFile->wh, sizeof(WAVE_HEADER), 1, wFile->file);
#ifdef _BIG_ENDIAN
            FlipWaveHeader(&wFile->wh);
#endif
        }
    }

    /* close the file */
    if (fclose(wFile->file) != 0)
        count = 0;

    /* free the memory */
    free(wFile);

    /* return the file handle */
    return (count == 1 ? EAS_TRUE : EAS_FALSE);
} /* end WaveFileClose */

#ifdef _WAVE_FILE_READ
#ifdef _BIG_ENDIAN
#error "WaveFileOpen not currently supported on big-endian processors"
#endif
/*----------------------------------------------------------------------------
 * WaveFileOpen()
 *----------------------------------------------------------------------------
 * Purpose: Opens a wave file for reading and reads the header
 *
 * Inputs:
 *
 * Outputs:
 *
 *----------------------------------------------------------------------------
*/

WAVE_FILE *WaveFileOpen (const char *filename)
{
    WAVE_FILE *wFile;
    struct
    {
        EAS_U32 tag;
        EAS_U32 size;
    } chunk;
    EAS_U32 tag;
    EAS_I32 startChunkPos;
    EAS_INT state;
    EAS_BOOL done;

    /* allocate memory */
    wFile = malloc(sizeof(WAVE_FILE));
    if (!wFile)
        return NULL;

    /* open the file */
    wFile->write = EAS_FALSE;
    wFile->file = fopen(filename,"rb");
    if (!wFile->file)
    {
        free(wFile);
        return NULL;
    }

    /* make lint happy */
    chunk.tag = chunk.size = 0;
    startChunkPos = 0;

    /* read the RIFF tag and file size */
    state = 0;
    done = EAS_FALSE;
    while (!done)
    {

        switch(state)
        {
            /* read the RIFF tag */
            case 0:
                if (fread(&chunk, sizeof(chunk), 1, wFile->file) != 1)
                    done = EAS_TRUE;
                else
                {
                    if (chunk.tag != riffTag)
                        done = EAS_TRUE;
                    else
                        state++;
                }
                break;

            /* read the WAVE tag */
            case 1:
                if (fread(&tag, sizeof(tag), 1, wFile->file) != 1)
                    done = EAS_TRUE;
                else
                {
                    if (tag != waveTag)
                        done = EAS_TRUE;
                    else
                        state++;
                }
                break;

            /* looking for fmt chunk */
            case 2:
                if (fread(&chunk, sizeof(chunk), 1, wFile->file) != 1)
                    done = EAS_TRUE;
                else
                {
                    startChunkPos = ftell(wFile->file);

                    /* not fmt tag, skip it */
                    if (chunk.tag != fmtTag)
                        fseek(wFile->file, startChunkPos + (EAS_I32) chunk.size, SEEK_SET);
                    else
                        state++;
                }
                break;

            /* read fmt chunk */
            case 3:
                if (fread(&wFile->wh.fc, sizeof(FMT_CHUNK), 1, wFile->file) != 1)
                    done = EAS_TRUE;
                else
                {
                    fseek(wFile->file, startChunkPos + (EAS_I32) chunk.size, SEEK_SET);
                    state++;
                }
                break;

            /* looking for data chunk */
            case 4:
                if (fread(&chunk, sizeof(chunk), 1, wFile->file) != 1)
                    done = EAS_TRUE;
                else
                {
                    startChunkPos = ftell(wFile->file);

                    /* not data tag, skip it */
                    if (chunk.tag != dataTag)
                        fseek(wFile->file, startChunkPos + (EAS_I32) chunk.size, SEEK_SET);
                    else
                    {
                        wFile->dataSize = chunk.size;
                        state++;
                        done = EAS_TRUE;
                    }
                }
                break;

            default:
                done = EAS_TRUE;
                break;
        }
    }

    /* if not final state, an error occurred */
    if (state != 5)
    {
        fclose(wFile->file);
        free(wFile);
        return NULL;
    }

    /* return the file handle */
    return wFile;
} /* end WaveFileOpen */
#endif