/*
    SDL_mixer:  An audio mixer library based on the SDL library
    Copyright (C) 1997-2004 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    Sam Lantinga
    slouken@libsdl.org
*/

/* $Id: wavestream.c 2356 2006-05-09 08:02:35Z slouken $ */

/* This file supports streaming WAV files, without volume adjustment */

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

#include "SDL_audio.h"
#include "SDL_mutex.h"
#include "SDL_rwops.h"
#include "SDL_endian.h"

#include "SDL_mixer.h"
#include "wavestream.h"

/*
    Taken with permission from SDL_wave.h, part of the SDL library,
    available at: http://www.libsdl.org/
    and placed under the same license as this mixer library.
*/

/* WAVE files are little-endian */

/*******************************************/
/* Define values for Microsoft WAVE format */
/*******************************************/
#define RIFF		0x46464952		/* "RIFF" */
#define WAVE		0x45564157		/* "WAVE" */
#define FACT		0x74636166		/* "fact" */
#define LIST		0x5453494c		/* "LIST" */
#define FMT		0x20746D66		/* "fmt " */
#define DATA		0x61746164		/* "data" */
#define PCM_CODE	1
#define ADPCM_CODE	2
#define WAVE_MONO	1
#define WAVE_STEREO	2

/* Normally, these three chunks come consecutively in a WAVE file */
typedef struct WaveFMT {
/* Not saved in the chunk we read:
	Uint32	FMTchunk;
	Uint32	fmtlen;
*/
	Uint16	encoding;	
	Uint16	channels;		/* 1 = mono, 2 = stereo */
	Uint32	frequency;		/* One of 11025, 22050, or 44100 Hz */
	Uint32	byterate;		/* Average bytes per second */
	Uint16	blockalign;		/* Bytes per sample block */
	Uint16	bitspersample;		/* One of 8, 12, 16, or 4 for ADPCM */
} WaveFMT;

/* The general chunk found in the WAVE file */
typedef struct Chunk {
	Uint32 magic;
	Uint32 length;
	Uint8 *data;			/* Data includes magic and length */
} Chunk;

/*********************************************/
/* Define values for AIFF (IFF audio) format */
/*********************************************/
#define FORM		0x4d524f46		/* "FORM" */
#define AIFF		0x46464941		/* "AIFF" */
#define SSND		0x444e5353		/* "SSND" */
#define COMM		0x4d4d4f43		/* "COMM" */


/* Currently we only support a single stream at a time */
static WAVStream *music = NULL;

/* This is the format of the audio mixer data */
static SDL_AudioSpec mixer;
static int wavestream_volume = MIX_MAX_VOLUME;

/* Function to load the WAV/AIFF stream */
static FILE *LoadWAVStream (const char *file, SDL_AudioSpec *spec,
					long *start, long *stop);
static FILE *LoadAIFFStream (const char *file, SDL_AudioSpec *spec,
					long *start, long *stop);

/* Initialize the WAVStream player, with the given mixer settings
   This function returns 0, or -1 if there was an error.
 */
int WAVStream_Init(SDL_AudioSpec *mixerfmt)
{
	mixer = *mixerfmt;
	return(0);
}

void WAVStream_SetVolume(int volume)
{
	wavestream_volume = volume;
}

/* Load a WAV stream from the given file */
WAVStream *WAVStream_LoadSong(const char *file, const char *magic)
{
	WAVStream *wave;
	SDL_AudioSpec wavespec;

	if ( ! mixer.format ) {
		Mix_SetError("WAV music output not started");
		return(NULL);
	}
	wave = (WAVStream *)malloc(sizeof *wave);
	if ( wave ) {
		memset(wave, 0, (sizeof *wave));
		if ( strcmp(magic, "RIFF") == 0 ) {
			wave->wavefp = LoadWAVStream(file, &wavespec,
					&wave->start, &wave->stop);
		} else
		if ( strcmp(magic, "FORM") == 0 ) {
			wave->wavefp = LoadAIFFStream(file, &wavespec,
					&wave->start, &wave->stop);
		} else {
			Mix_SetError("Unknown WAVE format");
		}
		if ( wave->wavefp == NULL ) {
			free(wave);
			return(NULL);
		}
		SDL_BuildAudioCVT(&wave->cvt,
			wavespec.format, wavespec.channels, wavespec.freq,
			mixer.format, mixer.channels, mixer.freq);
	}
	return(wave);
}

/* Start playback of a given WAV stream */
void WAVStream_Start(WAVStream *wave)
{
	clearerr(wave->wavefp);
	fseek(wave->wavefp, wave->start, SEEK_SET);
	music = wave;
}

/* Play some of a stream previously started with WAVStream_Start() */
void WAVStream_PlaySome(Uint8 *stream, int len)
{
	long pos;

	if ( music && ((pos=ftell(music->wavefp)) < music->stop) ) {
		if ( music->cvt.needed ) {
			int original_len;

			original_len=(int)((double)len/music->cvt.len_ratio);
			if ( music->cvt.len != original_len ) {
				int worksize;
				if ( music->cvt.buf != NULL ) {
					free(music->cvt.buf);
				}
				worksize = original_len*music->cvt.len_mult;
				music->cvt.buf=(Uint8 *)malloc(worksize);
				if ( music->cvt.buf == NULL ) {
					return;
				}
				music->cvt.len = original_len;
			}
			if ( (music->stop - pos) < original_len ) {
				original_len = (music->stop - pos);
			}
			original_len = fread(music->cvt.buf,1,original_len,music->wavefp);
			/* At least at the time of writing, SDL_ConvertAudio()
			   does byte-order swapping starting at the end of the
			   buffer. Thus, if we are reading 16-bit samples, we
			   had better make damn sure that we get an even
			   number of bytes, or we'll get garbage.
			 */
			if ( (music->cvt.src_format & 0x0010) && (original_len & 1) ) {
				original_len--;
			}
			music->cvt.len = original_len;
			SDL_ConvertAudio(&music->cvt);
			SDL_MixAudio(stream, music->cvt.buf, music->cvt.len_cvt, wavestream_volume);
		} else {
			Uint8 *data;
			if ( (music->stop - pos) < len ) {
				len = (music->stop - pos);
			}
			data = SDL_stack_alloc(Uint8, len);
			if (data)
			{		
				fread(data, len, 1, music->wavefp);
				SDL_MixAudio(stream, data, len, wavestream_volume);
				SDL_stack_free(data);
			}	
		}
	}
}

/* Stop playback of a stream previously started with WAVStream_Start() */
void WAVStream_Stop(void)
{
	music = NULL;
}

/* Close the given WAV stream */
void WAVStream_FreeSong(WAVStream *wave)
{
	if ( wave ) {
		/* Clean up associated data */
		if ( wave->wavefp ) {
			fclose(wave->wavefp);
		}
		if ( wave->cvt.buf ) {
			free(wave->cvt.buf);
		}
		free(wave);
	}
}

/* Return non-zero if a stream is currently playing */
int WAVStream_Active(void)
{
	int active;

	active = 0;
	if ( music && (ftell(music->wavefp) < music->stop) ) {
		active = 1;
	}
	return(active);
}

static int ReadChunk(SDL_RWops *src, Chunk *chunk, int read_data)
{
	chunk->magic	= SDL_ReadLE32(src);
	chunk->length	= SDL_ReadLE32(src);
	if ( read_data ) {
		chunk->data = (Uint8 *)malloc(chunk->length);
		if ( chunk->data == NULL ) {
			Mix_SetError("Out of memory");
			return(-1);
		}
		if ( SDL_RWread(src, chunk->data, chunk->length, 1) != 1 ) {
			Mix_SetError("Couldn't read chunk");
			free(chunk->data);
			return(-1);
		}
	} else {
		SDL_RWseek(src, chunk->length, SEEK_CUR);
	}
	return(chunk->length);
}

static FILE *LoadWAVStream (const char *file, SDL_AudioSpec *spec,
					long *start, long *stop)
{
	int was_error;
	FILE *wavefp;
	SDL_RWops *src;
	Chunk chunk;
	int lenread;

	/* WAV magic header */
	Uint32 RIFFchunk;
	Uint32 wavelen;
	Uint32 WAVEmagic;

	/* FMT chunk */
	WaveFMT *format = NULL;

	/* Make sure we are passed a valid data source */
	was_error = 0;
	wavefp = fopen(file, "rb");
	src = NULL;
	if ( wavefp ) {
		src = SDL_RWFromFP(wavefp, 0);
	}
	if ( src == NULL ) {
		was_error = 1;
		goto done;
	}

	/* Check the magic header */
	RIFFchunk	= SDL_ReadLE32(src);
	wavelen		= SDL_ReadLE32(src);
	WAVEmagic	= SDL_ReadLE32(src);
	if ( (RIFFchunk != RIFF) || (WAVEmagic != WAVE) ) {
		Mix_SetError("Unrecognized file type (not WAVE)");
		was_error = 1;
		goto done;
	}

	/* Read the audio data format chunk */
	chunk.data = NULL;
	do {
		/* FIXME! Add this logic to SDL_LoadWAV_RW() */
		if ( chunk.data ) {
			free(chunk.data);
		}
		lenread = ReadChunk(src, &chunk, 1);
		if ( lenread < 0 ) {
			was_error = 1;
			goto done;
		}
	} while ( (chunk.magic == FACT) || (chunk.magic == LIST) );

	/* Decode the audio data format */
	format = (WaveFMT *)chunk.data;
	if ( chunk.magic != FMT ) {
		free(chunk.data);
		Mix_SetError("Complex WAVE files not supported");
		was_error = 1;
		goto done;
	}
	switch (SDL_SwapLE16(format->encoding)) {
		case PCM_CODE:
			/* We can understand this */
			break;
		default:
			Mix_SetError("Unknown WAVE data format");
			was_error = 1;
			goto done;
	}
	memset(spec, 0, (sizeof *spec));
	spec->freq = SDL_SwapLE32(format->frequency);
	switch (SDL_SwapLE16(format->bitspersample)) {
		case 8:
			spec->format = AUDIO_U8;
			break;
		case 16:
			spec->format = AUDIO_S16;
			break;
		default:
			Mix_SetError("Unknown PCM data format");
			was_error = 1;
			goto done;
	}
	spec->channels = (Uint8) SDL_SwapLE16(format->channels);
	spec->samples = 4096;		/* Good default buffer size */

	/* Set the file offset to the DATA chunk data */
	chunk.data = NULL;
	do {
		*start = SDL_RWtell(src) + 2*sizeof(Uint32);
		lenread = ReadChunk(src, &chunk, 0);
		if ( lenread < 0 ) {
			was_error = 1;
			goto done;
		}
	} while ( chunk.magic != DATA );
	*stop = SDL_RWtell(src);

done:
	if ( format != NULL ) {
		free(format);
	}
	if ( src ) {
		SDL_RWclose(src);
	}
	if ( was_error ) {
		if ( wavefp ) {
			fclose(wavefp);
			wavefp = NULL;
		}
	}
	return(wavefp);
}

/* I couldn't get SANE_to_double() to work, so I stole this from libsndfile.
 * I don't pretend to fully understand it.
 */

static Uint32 SANE_to_Uint32 (Uint8 *sanebuf)
{
	/* Negative number? */
	if (sanebuf[0] & 0x80)
		return 0;

	/* Less than 1? */
	if (sanebuf[0] <= 0x3F)
		return 1;

	/* Way too big? */
	if (sanebuf[0] > 0x40)
		return 0x4000000;

	/* Still too big? */
	if (sanebuf[0] == 0x40 && sanebuf[1] > 0x1C)
		return 800000000;

	return ((sanebuf[2] << 23) | (sanebuf[3] << 15) | (sanebuf[4] << 7)
		| (sanebuf[5] >> 1)) >> (29 - sanebuf[1]);
}

static FILE *LoadAIFFStream (const char *file, SDL_AudioSpec *spec,
					long *start, long *stop)
{
	int was_error;
	int found_SSND;
	int found_COMM;
	FILE *wavefp;
	SDL_RWops *src;

	Uint32 chunk_type;
	Uint32 chunk_length;
	long next_chunk;

	/* AIFF magic header */
	Uint32 FORMchunk;
	Uint32 AIFFmagic;
	/* SSND chunk        */
	Uint32 offset;
	Uint32 blocksize;
	/* COMM format chunk */
	Uint16 channels = 0;
	Uint32 numsamples = 0;
	Uint16 samplesize = 0;
	Uint8 sane_freq[10];
	Uint32 frequency = 0;


	/* Make sure we are passed a valid data source */
	was_error = 0;
	wavefp = fopen(file, "rb");
	src = NULL;
	if ( wavefp ) {
		src = SDL_RWFromFP(wavefp, 0);
	}
	if ( src == NULL ) {
		was_error = 1;
		goto done;
	}

	/* Check the magic header */
	FORMchunk	= SDL_ReadLE32(src);
	chunk_length	= SDL_ReadBE32(src);
	AIFFmagic	= SDL_ReadLE32(src);
	if ( (FORMchunk != FORM) || (AIFFmagic != AIFF) ) {
		Mix_SetError("Unrecognized file type (not AIFF)");
		was_error = 1;
		goto done;
	}

	/* From what I understand of the specification, chunks may appear in
         * any order, and we should just ignore unknown ones.
	 *
	 * TODO: Better sanity-checking. E.g. what happens if the AIFF file
	 *       contains compressed sound data?
         */

	found_SSND = 0;
	found_COMM = 0;

	do {
	    chunk_type		= SDL_ReadLE32(src);
	    chunk_length	= SDL_ReadBE32(src);
	    next_chunk		= SDL_RWtell(src) + chunk_length;

	    /* Paranoia to avoid infinite loops */
	    if (chunk_length == 0)
		break;

            switch (chunk_type) {
		case SSND:
		    found_SSND		= 1;
		    offset		= SDL_ReadBE32(src);
		    blocksize		= SDL_ReadBE32(src);
		    *start		= SDL_RWtell(src) + offset;
		    break;

		case COMM:
		    found_COMM		= 1;

		    /* Read the audio data format chunk */
		    channels		= SDL_ReadBE16(src);
		    numsamples		= SDL_ReadBE32(src);
		    samplesize		= SDL_ReadBE16(src);
		    SDL_RWread(src, sane_freq, sizeof(sane_freq), 1);
		    frequency		= SANE_to_Uint32(sane_freq);
		    break;

		default:
		    break;
	    }
	} while ((!found_SSND || !found_COMM)
		 && SDL_RWseek(src, next_chunk, SEEK_SET) != -1);

	if (!found_SSND) {
	    Mix_SetError("Bad AIFF file (no SSND chunk)");
	    was_error = 1;
	    goto done;
	}
		    
	if (!found_COMM) {
	    Mix_SetError("Bad AIFF file (no COMM chunk)");
	    was_error = 1;
	    goto done;
	}

	*stop = *start + channels * numsamples * (samplesize / 8);

	/* Decode the audio data format */
	memset(spec, 0, (sizeof *spec));
	spec->freq = frequency;
	switch (samplesize) {
		case 8:
			spec->format = AUDIO_S8;
			break;
		case 16:
			spec->format = AUDIO_S16MSB;
			break;
		default:
			Mix_SetError("Unknown samplesize in data format");
			was_error = 1;
			goto done;
	}
	spec->channels = (Uint8) channels;
	spec->samples = 4096;		/* Good default buffer size */

done:
	if ( src ) {
		SDL_RWclose(src);
	}
	if ( was_error ) {
		if ( wavefp ) {
			fclose(wavefp);
			wavefp = NULL;
		}
	}
	return(wavefp);
}

