/*
    SDL_mixer:  An audio mixer library based on the SDL library
    Copyright (C) 1997-2009 Sam Lantinga

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.

    This is the source needed to decode a Creative Labs VOC file into a
    waveform. It's pretty straightforward once you get going. The only
    externally-callable function is Mix_LoadVOC_RW(), which is meant to
    act as identically to SDL_LoadWAV_RW() as possible.

    This file by Ryan C. Gordon (icculus@icculus.org).

    Heavily borrowed from sox v12.17.1's voc.c.
        (http://www.freshmeat.net/projects/sox/)
*/

/* $Id: load_voc.c 5214 2009-11-08 17:11:09Z slouken $ */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "SDL_mutex.h"
#include "SDL_endian.h"
#include "SDL_timer.h"

#include "SDL_mixer.h"
#include "load_voc.h"

/* Private data for VOC file */
typedef struct vocstuff {
	Uint32	rest;			/* bytes remaining in current block */
	Uint32	rate;			/* rate code (byte) of this chunk */
	int 	silent;		/* sound or silence? */
	Uint32	srate;			/* rate code (byte) of silence */
	Uint32	blockseek;		/* start of current output block */
	Uint32	samples;		/* number of samples output */
	Uint32	size;		/* word length of data */
	Uint8 	channels;	/* number of sound channels */
	int     has_extended;       /* Has an extended block been read? */
} vs_t;

/* Size field */ 
/* SJB: note that the 1st 3 are sometimes used as sizeof(type) */
#define	ST_SIZE_BYTE	1
#define ST_SIZE_8BIT	1
#define	ST_SIZE_WORD	2
#define ST_SIZE_16BIT	2
#define	ST_SIZE_DWORD	4
#define ST_SIZE_32BIT	4
#define	ST_SIZE_FLOAT	5
#define ST_SIZE_DOUBLE	6
#define ST_SIZE_IEEE	7	/* IEEE 80-bit floats. */

/* Style field */
#define ST_ENCODING_UNSIGNED	1 /* unsigned linear: Sound Blaster */
#define ST_ENCODING_SIGN2	2 /* signed linear 2's comp: Mac */
#define	ST_ENCODING_ULAW	3 /* U-law signed logs: US telephony, SPARC */
#define ST_ENCODING_ALAW	4 /* A-law signed logs: non-US telephony */
#define ST_ENCODING_ADPCM	5 /* Compressed PCM */
#define ST_ENCODING_IMA_ADPCM	6 /* Compressed PCM */
#define ST_ENCODING_GSM		7 /* GSM 6.10 33-byte frame lossy compression */

#define	VOC_TERM	0
#define	VOC_DATA	1
#define	VOC_CONT	2
#define	VOC_SILENCE	3
#define	VOC_MARKER	4
#define	VOC_TEXT	5
#define	VOC_LOOP	6
#define	VOC_LOOPEND	7
#define VOC_EXTENDED    8
#define VOC_DATA_16	9


static int voc_check_header(SDL_RWops *src)
{
    /* VOC magic header */
    Uint8  signature[20];  /* "Creative Voice File\032" */
    Uint16 datablockofs;

    SDL_RWseek(src, 0, RW_SEEK_SET);

    if (SDL_RWread(src, signature, sizeof (signature), 1) != 1)
        return(0);

    if (memcmp(signature, "Creative Voice File\032", sizeof (signature)) != 0) {
        SDL_SetError("Unrecognized file type (not VOC)");
        return(0);
    }

        /* get the offset where the first datablock is located */
    if (SDL_RWread(src, &datablockofs, sizeof (Uint16), 1) != 1)
        return(0);

    datablockofs = SDL_SwapLE16(datablockofs);

    if (SDL_RWseek(src, datablockofs, RW_SEEK_SET) != datablockofs)
        return(0);

    return(1);  /* success! */
} /* voc_check_header */


/* Read next block header, save info, leave position at start of data */
static int voc_get_block(SDL_RWops *src, vs_t *v, SDL_AudioSpec *spec)
{
    Uint8 bits24[3];
    Uint8 uc, block;
    Uint32 sblen;
    Uint16 new_rate_short;
    Uint32 new_rate_long;
    Uint8 trash[6];
    Uint16 period;
    unsigned int i;

    v->silent = 0;
    while (v->rest == 0)
    {
        if (SDL_RWread(src, &block, sizeof (block), 1) != 1)
            return 1;  /* assume that's the end of the file. */

        if (block == VOC_TERM)
            return 1;

        if (SDL_RWread(src, bits24, sizeof (bits24), 1) != 1)
            return 1;  /* assume that's the end of the file. */
        
        /* Size is an 24-bit value. Ugh. */
        sblen = ( (bits24[0]) | (bits24[1] << 8) | (bits24[2] << 16) );

        switch(block)
        {
            case VOC_DATA:
                if (SDL_RWread(src, &uc, sizeof (uc), 1) != 1)
                    return 0;

                /* When DATA block preceeded by an EXTENDED     */
                /* block, the DATA blocks rate value is invalid */
                if (!v->has_extended)
                {
                    if (uc == 0)
                    {
                        SDL_SetError("VOC Sample rate is zero?");
                        return 0;
                    }

                    if ((v->rate != -1) && (uc != v->rate))
                    {
                        SDL_SetError("VOC sample rate codes differ");
                        return 0;
                    }

                    v->rate = uc;
                    spec->freq = (Uint16)(1000000.0/(256 - v->rate));
                    v->channels = 1;
                }

                if (SDL_RWread(src, &uc, sizeof (uc), 1) != 1)
                    return 0;

                if (uc != 0)
                {
                    SDL_SetError("VOC decoder only interprets 8-bit data");
                    return 0;
                }

                v->has_extended = 0;
                v->rest = sblen - 2;
                v->size = ST_SIZE_BYTE;
                return 1;

            case VOC_DATA_16:
                if (SDL_RWread(src, &new_rate_long, sizeof (new_rate_long), 1) != 1)
                    return 0;
                new_rate_long = SDL_SwapLE32(new_rate_long);
                if (new_rate_long == 0)
                {
                    SDL_SetError("VOC Sample rate is zero?");
                    return 0;
                }
                if ((v->rate != -1) && (new_rate_long != v->rate))
                {
                    SDL_SetError("VOC sample rate codes differ");
                    return 0;
                }
                v->rate = new_rate_long;
                spec->freq = new_rate_long;

                if (SDL_RWread(src, &uc, sizeof (uc), 1) != 1)
                    return 0;

                switch (uc)
                {
                    case 8:  v->size = ST_SIZE_BYTE; break;
                    case 16: v->size = ST_SIZE_WORD; break;
                    default:
                        SDL_SetError("VOC with unknown data size");
                        return 0;
                }

                if (SDL_RWread(src, &v->channels, sizeof (Uint8), 1) != 1)
                    return 0;

                if (SDL_RWread(src, trash, sizeof (Uint8), 6) != 6)
                    return 0;

                v->rest = sblen - 12;
                return 1;

            case VOC_CONT:
                v->rest = sblen;
                return 1;

            case VOC_SILENCE:
                if (SDL_RWread(src, &period, sizeof (period), 1) != 1)
                    return 0;
                period = SDL_SwapLE16(period);

                if (SDL_RWread(src, &uc, sizeof (uc), 1) != 1)
                    return 0;
                if (uc == 0)
                {
                    SDL_SetError("VOC silence sample rate is zero");
                    return 0;
                }

                /*
                 * Some silence-packed files have gratuitously
                 * different sample rate codes in silence.
                 * Adjust period.
                 */
                if ((v->rate != -1) && (uc != v->rate))
                    period = (Uint16)((period * (256 - uc))/(256 - v->rate));
                else
                    v->rate = uc;
                v->rest = period;
                v->silent = 1;
                return 1;

            case VOC_LOOP:
            case VOC_LOOPEND:
                for(i = 0; i < sblen; i++)   /* skip repeat loops. */
                {
                    if (SDL_RWread(src, trash, sizeof (Uint8), 1) != 1)
                        return 0;
                }
                break;

            case VOC_EXTENDED:
                /* An Extended block is followed by a data block */
                /* Set this byte so we know to use the rate      */
                /* value from the extended block and not the     */
                /* data block.                     */
                v->has_extended = 1;
                if (SDL_RWread(src, &new_rate_short, sizeof (new_rate_short), 1) != 1)
                    return 0;
                new_rate_short = SDL_SwapLE16(new_rate_short);
                if (new_rate_short == 0)
                {
                   SDL_SetError("VOC sample rate is zero");
                   return 0;
                }
                if ((v->rate != -1) && (new_rate_short != v->rate))
                {
                   SDL_SetError("VOC sample rate codes differ");
                   return 0;
                }
                v->rate = new_rate_short;

                if (SDL_RWread(src, &uc, sizeof (uc), 1) != 1)
                    return 0;

                if (uc != 0)
                {
                    SDL_SetError("VOC decoder only interprets 8-bit data");
                    return 0;
                }

                if (SDL_RWread(src, &uc, sizeof (uc), 1) != 1)
                    return 0;

                if (uc)
                    spec->channels = 2;  /* Stereo */
                /* Needed number of channels before finishing
                   compute for rate */
                spec->freq = (256000000L/(65536L - v->rate))/spec->channels;
                /* An extended block must be followed by a data */
                /* block to be valid so loop back to top so it  */
                /* can be grabed.                */
                continue;

            case VOC_MARKER:
                if (SDL_RWread(src, trash, sizeof (Uint8), 2) != 2)
                    return 0;

                /* Falling! Falling! */

            default:  /* text block or other krapola. */
                for(i = 0; i < sblen; i++)
                {
                    if (SDL_RWread(src, &trash, sizeof (Uint8), 1) != 1)
                        return 0;
                }

                if (block == VOC_TEXT)
                    continue;    /* get next block */
        }
    }

    return 1;
}


static int voc_read(SDL_RWops *src, vs_t *v, Uint8 *buf, SDL_AudioSpec *spec)
{
    int done = 0;
    Uint8 silence = 0x80;

    if (v->rest == 0)
    {
        if (!voc_get_block(src, v, spec))
            return 0;
    }

    if (v->rest == 0)
        return 0;

    if (v->silent)
    {
        if (v->size == ST_SIZE_WORD)
            silence = 0x00;

        /* Fill in silence */
        memset(buf, silence, v->rest);
        done = v->rest;
        v->rest = 0;
    }

    else
    {
        done = SDL_RWread(src, buf, 1, v->rest);
        v->rest -= done;
        if (v->size == ST_SIZE_WORD)
        {
            #if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
                Uint16 *samples = (Uint16 *)buf;
                for (; v->rest > 0; v->rest -= 2)
                {
                    *samples = SDL_SwapLE16(*samples);
                    samples++;
                }
            #endif
            done >>= 1;
        }
    }

    return done;
} /* voc_read */


/* don't call this directly; use Mix_LoadWAV_RW() for now. */
SDL_AudioSpec *Mix_LoadVOC_RW (SDL_RWops *src, int freesrc,
        SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len)
{
    vs_t v;
    int was_error = 1;
    int samplesize;
    Uint8 *fillptr;
    void *ptr;

    if ( (!src) || (!audio_buf) || (!audio_len) )   /* sanity checks. */
        goto done;

    if ( !voc_check_header(src) )
        goto done;

    v.rate = -1;
    v.rest = 0;
    v.has_extended = 0;
    *audio_buf = NULL;
    *audio_len = 0;
    memset(spec, '\0', sizeof (SDL_AudioSpec));

    if (!voc_get_block(src, &v, spec))
        goto done;

    if (v.rate == -1)
    {
        SDL_SetError("VOC data had no sound!");
        goto done;
    }

    spec->format = ((v.size == ST_SIZE_WORD) ? AUDIO_S16 : AUDIO_U8);
    if (spec->channels == 0)
        spec->channels = v.channels;

    *audio_len = v.rest;
    *audio_buf = malloc(v.rest);
    if (*audio_buf == NULL)
        goto done;

    fillptr = *audio_buf;

    while (voc_read(src, &v, fillptr, spec) > 0)
    {
        if (!voc_get_block(src, &v, spec))
            goto done;

        *audio_len += v.rest;
        ptr = realloc(*audio_buf, *audio_len);
        if (ptr == NULL)
        {
            free(*audio_buf);
            *audio_buf = NULL;
            *audio_len = 0;
            goto done;
        }

        *audio_buf = ptr;
        fillptr = ((Uint8 *) ptr) + (*audio_len - v.rest);
    }

    spec->samples = (Uint16)(*audio_len / v.size);

    was_error = 0;  /* success, baby! */

    /* Don't return a buffer that isn't a multiple of samplesize */
    samplesize = ((spec->format & 0xFF)/8)*spec->channels;
    *audio_len &= ~(samplesize-1);

done:
    if (src)
    {
        if (freesrc)
            SDL_RWclose(src);
        else
            SDL_RWseek(src, 0, RW_SEEK_SET);
    }

    if ( was_error )
        spec = NULL;

    return(spec);
} /* Mix_LoadVOC_RW */

/* end of load_voc.c ... */
