/***************************************************************************
 *   Copyright (C) 2005 by Michal Turek - Woq                              *
 *   WOQ (zavinac) seznam.cz                                               *
 *                                                                         *
 *   Program demonstruje prehravani vice zvuku najednou. Jeden bude        *
 *   hudbou ve smycce na pozadi a druhy bude spousten vzdy po stisku       *
 *   mezerniku. Pri nahravani funkci LoadSound() se zvuky konvertuji       *
 *   na stejny (hardwarovy) format.                                        *
 *                                                                         *
 *   btw, omlouvam se za ty zvuky, ale nic jineho jsem na disku nenasel :( *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program 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 General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <SDL.h>


/*
 * Symbolicke konstanty
 */

#define SDL_SUBSYSTEMS SDL_INIT_VIDEO|SDL_INIT_AUDIO
#define WIN_WIDTH 320
#define WIN_HEIGHT 240
#define WIN_BPP 0
#define WIN_TITLE "Konverze zvuku"

#define NUM_SOUNDS 2

// Pomocna struktura pro zvuky
struct SOUND
{
	Uint8 *data;	// Ukazatel na data
	Uint32 len;	// Delka dat
	Uint32 pos;	// Pozice pri prehravani

	// Kdyz len == pos, zvuk neni prehravan
};


/*
 * Funkcni prototypy
 */

void AudioCallback(void *unused, Uint8 *stream, int len);
bool Init();					// Inicializace
void Destroy();					// Deinicializace
bool ProcessEvent();				// Osetruje udalosti
int  main(int argc, char *argv[]);		// Vstup do programu

// Nahraje zvuk z filename, zkonvertuje ho na spec format a ulozi do sound
// Pamet na sound->data MUSI BYT po skonceni pouzivani uvolnena
bool LoadSound(const char *filename, const SDL_AudioSpec *spec, SOUND *sound);


/*
 * Globalni promenne
 */

Uint8 g_win_flags = SDL_HWSURFACE|SDL_DOUBLEBUF|SDL_RESIZABLE;
SDL_Surface *g_screen;

// Index 0 - hudba (smycka)
// Index 1 - zvuk spousteny stiskem mezerniku
SOUND g_sounds[NUM_SOUNDS];


/*
 * Audio callback funkce, posila data do streamu
 */

void AudioCallback(void *unused, Uint8 *stream, int len)
{
	// Hudba (smycka), stejne jako minule

	// Ukazatel na cast, kde se ma zacit prehravat
	Uint8 *wave_ptr = g_sounds[0].data + g_sounds[0].pos;

	// Delka zvuku do konce
	int wave_left = g_sounds[0].len - g_sounds[0].pos;

	// Zbyvajici delka je mensi nez pozadovana
	// Cyklus, protoze cely zvuk muze byt kratsi
	while(wave_left <= len)
	{
		// Posle data na zvukovou kartu
		SDL_MixAudio(stream, wave_ptr, wave_left, SDL_MIX_MAXVOLUME);

		// Posune se o prave zapsana data
		stream += wave_left;
		len -= wave_left;

		// Od zacatku zvuku
		wave_ptr = g_sounds[0].data;
		wave_left = g_sounds[0].len;
		g_sounds[0].pos = 0;
	}

	// Je jistota, ze zbyvajici cast zvuku je delsi nez pozadovana
	SDL_MixAudio(stream, wave_ptr, len, SDL_MIX_MAXVOLUME);
	g_sounds[0].pos += len;


	// Ostatni zvuky
	for(int i = 1; i < NUM_SOUNDS; i++)
	{
		// Delka zbyvajicich dat
		Sint32 amount = (g_sounds[i].len - g_sounds[i].pos);

		// Test preteceni
		if(amount > len)
			amount = len;

		// Posle data do streamu
		SDL_MixAudio(stream, &g_sounds[i].data[g_sounds[i].pos],
				amount, SDL_MIX_MAXVOLUME);

		// Posune se o prehrany usek
		g_sounds[i].pos += amount;
	}
}


/*
 * Nahraje zvuk ze souboru filename (.WAV), zkonvertuje ho na spec format
 * a vysledek ulozi na adresu sound
 * Pamet na sound->data MUSI BYT pred zacatkem i po skonceni pouzivani uvolnena
 */

bool LoadSound(const char *filename, const SDL_AudioSpec *spec, SOUND *sound)
{
	SDL_AudioCVT cvt;	// Konverzni struktura
	SDL_AudioSpec wave;	// Format puvodniho zvuku
	Uint8 *data;		// Pomocny ukazatel na data
	Uint32 len;		// Jejich delka

	// Nahraje zvuk
	if(SDL_LoadWAV(filename, &wave, &data, &len) == NULL)
	{
		fprintf(stderr, "Unable to load %s: %s\n",
				filename, SDL_GetError());
		return false;
	}

	// Vytvori konverzni strukturu
	if(SDL_BuildAudioCVT(&cvt, wave.format, wave.channels, wave.freq,
			spec->format, spec->channels, spec->freq) == -1)
	{
		fprintf(stderr, "Unable to build audio CVT (%s): %s\n",
				filename, SDL_GetError());
		SDL_FreeWAV(data);
		return false;
	}

	// Alokace pameti pro zkonvertovana data
	if((cvt.buf = (Uint8 *)malloc(len * cvt.len_mult)) == NULL)
	{
		fprintf(stderr, "Unable to allocate memory.\n");
		SDL_FreeWAV(data);
		return false;
	}

	// Zkopiruje nahrana data
	memcpy(cvt.buf, data, len);
	cvt.len = len;

	// Puvodni data uz nejsou potreba
	SDL_FreeWAV(data);

	// Konverze na pozadovany format
	if(SDL_ConvertAudio(&cvt) == -1)
	{
		fprintf(stderr, "Unable to convert audio (%s): %s\n",
				filename, SDL_GetError());
		free(cvt.buf);
		return false;
	}

	// Preda vystup pres parametr
	if(sound->data != NULL)// Kdyby nahodou...
		free(sound->data);

	sound->data = cvt.buf;
	sound->len = (Uint32)(cvt.len * cvt.len_ratio);
	sound->pos = sound->len;// Implicitne pozastaveny

	return true;
}


/*
 * Inicializacni funkce
 */

bool Init()
{
	// Inicializace SDL
	if(SDL_Init(SDL_SUBSYSTEMS) == -1)
	{
		fprintf(stderr, "Unable to initialize SDL: %s\n",
				SDL_GetError());
		return false;
	}

	// Nastaveni ikony
	SDL_Surface *icon = SDL_LoadBMP("./icon.bmp");
	if(icon != NULL)
	{
		SDL_WM_SetIcon(icon, NULL);
		SDL_FreeSurface(icon);
	}

	// Vytvori okno s definovanymi vlastnostmi
	g_screen = SDL_SetVideoMode(WIN_WIDTH, WIN_HEIGHT, WIN_BPP, g_win_flags);
	if(g_screen == NULL)
	{
		fprintf(stderr, "Unable to set %dx%d video: %s\n",
				WIN_WIDTH, WIN_HEIGHT, SDL_GetError());
		return false;
	}

	// Titulek okna
	SDL_WM_SetCaption(WIN_TITLE, NULL);

	// Nastaveni audia
	SDL_AudioSpec desired, obtained;
	desired.freq = 22050;// 16-bit. stereo na 22 kHz
	desired.format = AUDIO_S16;
	desired.channels = 2;
	desired.samples = 512;// Vhodne pro hry
	desired.callback = AudioCallback;
	desired.userdata = NULL;

	// Otevre audio zarizeni
	if(SDL_OpenAudio(&desired, &obtained) == -1)
	{
		fprintf(stderr, "Unable to open audio: %s\n", SDL_GetError());
		return false;
	}

	// Loading zvuku
	for(int i = 0; i < NUM_SOUNDS; i++)// Pro jistotu...
		g_sounds[i].data = NULL;

	SDL_LockAudio();
	if(!LoadSound("music.wav", &obtained, &g_sounds[0])
			|| !LoadSound("test.wav", &obtained, &g_sounds[1]))
	{
		return false;
	}
	SDL_UnlockAudio();

	// Zacne prehravat
	SDL_PauseAudio(0);

	return true;
}


/*
 * Deinicializacni funkce
 */

void Destroy()
{
	// Zavre audio a uvolni data wavu
	SDL_CloseAudio();

	// Uvolni data
	for(int i = 0; i < NUM_SOUNDS; i++)
	{
		if(g_sounds[i].data != NULL)
		{
			free(g_sounds[i].data);
			g_sounds[i].data = NULL;
		}
	}

	SDL_Quit();
}


/*
 * Osetreni udalosti
 */

bool ProcessEvent()
{
	SDL_Event event;

	while(SDL_WaitEvent(&event))
	{
		switch(event.type)
		{
		case SDL_KEYDOWN:
			switch(event.key.keysym.sym)
			{
			// Spusti zvuk
			case SDLK_SPACE:
				SDL_LockAudio();
				if(g_sounds[1].pos >= g_sounds[1].len)
					g_sounds[1].pos = 0;
				SDL_UnlockAudio();
				break;

			case SDLK_ESCAPE:
				return false;
				break;

			default:
				break;
			}
			break;

		case SDL_QUIT:
			return false;
			break;

		default:
			break;
		}
	}

	return true;
}


/*
 * Vstup do programu
 */

int main(int argc, char *argv[])
{
	printf(WIN_TITLE);
	printf("\nPress ESC key to quit.\n");

	// Inicializace
	if(!Init())
	{
		Destroy();
		return 1;
	}

	// Hlavni smycka programu
	bool done = false;
	while(!done)
	{
		done = !ProcessEvent();
	}

	// Deinicializace a konec
	Destroy();
	return 0;
}