/*(GPL)
------------------------------------------------------------
   Kobo Deluxe - Wrapper for Sound Control
------------------------------------------------------------
 * Copyright (C) 2007 David Olofson
 * 
 * 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.,
 * 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "sound.h"

#include "kobo.h"
#include "kobolog.h"
#include "random.h"
#include "audio.h"

int KOBO_sound::sounds_loaded = 0;
int KOBO_sound::music_loaded = 0;
int KOBO_sound::time = 0;
int KOBO_sound::_period = 30;
int KOBO_sound::sfx2d_tag = 0;
int KOBO_sound::listener_x = 0;
int KOBO_sound::listener_y = 0;
int KOBO_sound::wrap_x = 0;
int KOBO_sound::wrap_y = 0;
int KOBO_sound::scale = 65536 / 1000;
int KOBO_sound::panscale = 65536 / 700;
int KOBO_sound::firing = 0;
int KOBO_sound::overheat = 0;
unsigned KOBO_sound::rumble = 0;


KOBO_sound::KOBO_sound()
{
}


KOBO_sound::~KOBO_sound()
{
	close();
}


/*--------------------------------------------------
	Open/close
--------------------------------------------------*/


int KOBO_sound::load(int (*prog)(const char *msg), int force)
{
	int res;
	int save_to_disk = 0;
	const char *ap = fmap->get("SFX>>", FM_DIR);
	if(!ap)
	{
		log_printf(ELOG, "Couldn't find audio data directory!\n");
		return -1;
	}
	audio_set_path(ap);

	if(!sounds_loaded || force)
	{
		if(prog("Loading sound effects"))
			return -999;
		res = -1;
		if(prefs->cached_sounds && !force)
		{
			res = audio_wave_load(0, "sfx_c.agw", 0);
			if(res < 0)
				save_to_disk = 1;
		}
		if(res < 0)
			res = audio_wave_load(0, "sfx.agw", 0);
		if(res >= 0)
			sounds_loaded = 1;
		else
			log_printf(ELOG, "Could not load sound effects!\n");
		prog(NULL);
	}

	if(prefs->use_music && !music_loaded || force)
	{
		if(prog("Loading music"))
			return -999;
		res = -1;
		if(prefs->cached_sounds && !force)
		{
			res = audio_wave_load(0, "music_c.agw", 0);
			if(res < 0)
				save_to_disk = 1;
		}
		if(res < 0)
			res = audio_wave_load(0, "music.agw", 0);
		if(res >= 0)
			music_loaded = 1;
		else
			log_printf(ELOG, "Could not load music!\n");
		prog(NULL);
	}

	if(save_to_disk)
		if(prog("Preparing audio engine"))
			return -999;

	audio_wave_prepare(-1);

	if(save_to_disk)
	{
		if(prog("Writing sounds to disk"))
			return -999;
		if(audio_wave_load(0, "save_waves.agw", 0) < 0)
			log_printf(ELOG, "Could not save sounds to disk!\n");
	}

	audio_wave_info(-1);

	return prog(NULL);
}


void KOBO_sound::prefschange()
{
	/* Levels */
//	audio_master_volume((float)prefs->volume/100.0);
	audio_group_controlf(SOUND_GROUP_UI, ACC_VOLUME,
			(float)prefs->intro_vol/100.0);
	audio_group_controlf(SOUND_GROUP_UIMUSIC, ACC_VOLUME,
			(float)prefs->intro_vol/100.0);
	audio_group_controlf(SOUND_GROUP_SFX, ACC_VOLUME,
			(float)prefs->sfx_vol/100.0);
	audio_group_controlf(SOUND_GROUP_BGMUSIC, ACC_VOLUME,
			(float)prefs->music_vol/100.0);
	set_boost(prefs->vol_boost);
	audio_quality((audio_quality_t)prefs->mixquality);

	// Bus 7: Our "Master Reverb Bus"
	master_reverb((float)prefs->reverb/100.0);
}
	

int KOBO_sound::open()
{
	if(!prefs->use_sound)
	{
		log_printf(WLOG, "Sound disabled!\n");
		return 0;
	}

	if(audio_start(prefs->samplerate, prefs->latency, prefs->use_oss, prefs->cmd_midi,
			prefs->cmd_pollaudio) < 0)
	{
		log_printf(ELOG, "Couldn't initialize audio;"
				" disabling sound effects.\n");
		return -1;
	}

	// Channel grouping. We use only one chanel per group here, so we
	// just assign the first channels to the available groups.
	for(int i = 0; i < AUDIO_MAX_GROUPS; ++i)
		audio_channel_control(i, -1, ACC_GROUP, i);

	// Dirty hack for the music; the sequencer uses channels 16..31.
	for(int i = 16; i < 32; ++i)
		audio_channel_control(i, -1, ACC_GROUP, SOUND_GROUP_BGMUSIC);

	// Higher priority for music and UI effects
	audio_channel_control(SOUND_GROUP_BGMUSIC, AVT_ALL, ACC_PRIORITY, 1);
	audio_channel_control(SOUND_GROUP_UIMUSIC, AVT_ALL, ACC_PRIORITY, 2);
	audio_channel_control(SOUND_GROUP_UI, AVT_ALL, ACC_PRIORITY, 3);
	audio_channel_control(SOUND_GROUP_SFX, AVT_ALL, ACC_PRIORITY, 4);

	g_wrap(MAP_SIZEX*CHIP_SIZEX, MAP_SIZEY*CHIP_SIZEY);
	g_scale(VIEWLIMIT * 3 / 2, VIEWLIMIT);

	// For the noise "bzzzt" effect :-)
	audio_channel_control(3, -1, ACC_PRIM_BUS, 1);

	// Bus 0: Sound effects
	audio_bus_control(0, 1, ABC_FX_TYPE, AFX_NONE);
	audio_bus_controlf(0, 0, ABC_SEND_MASTER, 1.0);
	audio_bus_controlf(0, 0, ABC_SEND_BUS_7, 0.5); // Always a little rvb!

	// Bus 1: Sound effects with less reverb
	audio_bus_control(1, 1, ABC_FX_TYPE, AFX_NONE);
	audio_bus_controlf(1, 0, ABC_SEND_MASTER, 1.0);
	audio_bus_controlf(1, 0, ABC_SEND_BUS_7, 0.1);

	prefschange();

	time = SDL_GetTicks();
	return 0;
}


void KOBO_sound::stop()
{
	audio_stop();
}


void KOBO_sound::close()
{
	audio_close();
	sounds_loaded = 0;
	music_loaded = 0;
}



/*--------------------------------------------------
	Main controls
--------------------------------------------------*/

void KOBO_sound::master_reverb(float rvb)
{
	int master_rvb = (int)(rvb * 65536.0);
	audio_bus_controlf(7, 0, ABC_SEND_MASTER, 0.0);
	audio_bus_control(7, 1, ABC_SEND_MASTER, master_rvb);
	if(master_rvb)
		audio_bus_control(7, 1, ABC_FX_TYPE, AFX_REVERB);
	else
		audio_bus_control(7, 1, ABC_FX_TYPE, AFX_NONE);
}


void KOBO_sound::set_boost(int boost)
{
	switch(boost)
	{
	  case 0:
		audio_set_limiter(1.0, 5.0);
		break;
	  case 1:
		audio_set_limiter(.9, 4.5);
		break;
	  case 2:
	  default:
		audio_set_limiter(.75, 4.0);
		break;
	  case 3:
		audio_set_limiter(.5, 3.5);
		break;
	  case 4:
		audio_set_limiter(.25, 3.0);
		break;
	}
}


void KOBO_sound::sfx_volume(float vol)
{
	audio_group_controlf(SOUND_GROUP_SFX, ACC_VOLUME,
			(float)prefs->sfx_vol * vol / 100.0f);
}


void KOBO_sound::intro_volume(float vol)
{
	audio_group_controlf(SOUND_GROUP_UIMUSIC, ACC_VOLUME,
			(float)prefs->intro_vol * vol / 100.0f);
}


void KOBO_sound::music_volume(float vol)
{
	audio_group_controlf(SOUND_GROUP_BGMUSIC, ACC_VOLUME,
			(float)prefs->music_vol * vol / 100.0f);
}


void KOBO_sound::period(int ms)
{
	if(ms > 0)
		_period = ms;
	time = 0;
}


void KOBO_sound::frame()
{
#if 0
	// Positional audio test
	static int aaa = 0;
	if(++aaa > 20)
		aaa = 0;
	if(aaa == 10)
		g_play(SOUND_OVERHEAT, listener_x + 128, listener_y, 32768, 55<<16);
	else if (aaa == 20)
		g_play(SOUND_OVERHEAT, listener_x - 128, listener_y, 32768, 67<<16);
#endif
	// Advance to next game logic frame
	int nc = audio_next_callback();
	if(time < nc)
		time = nc;
	else
		time -= 1 + (labs(time - nc) >> 5);
	audio_bump(time);
	time += _period;

	// Various sound control logic
	++rumble;	// Rumble timer
}


void KOBO_sound::run()
{
	audio_run();
}


/*--------------------------------------------------
	In-game sound
--------------------------------------------------*/

void KOBO_sound::g_music(int wid)
{
	audio_channel_control(SOUND_GROUP_BGMUSIC, AVT_FUTURE, ACC_PATCH, wid);
	audio_channel_control(SOUND_GROUP_UI, AVT_FUTURE, ACC_PAN, 0);
	audio_channel_play(SOUND_GROUP_BGMUSIC, 0, 60<<16, 65536);
}


void KOBO_sound::play(int wid, int vol, int pitch, int pan)
{
	audio_channel_control(SOUND_GROUP_UI, AVT_FUTURE, ACC_PATCH, wid);
	audio_channel_control(SOUND_GROUP_UI, AVT_FUTURE, ACC_PAN, pan);
	audio_channel_play(SOUND_GROUP_UI, 0, pitch, vol);
}


void KOBO_sound::g_position(int x, int y)
{
	listener_x = x;
	listener_y = y;
}


void KOBO_sound::g_wrap(int w, int h)
{
	wrap_x = w;
	wrap_y = h;
}


void KOBO_sound::g_scale(int maxrange, int pan_maxrange)
{
	scale = 65536 / maxrange;
	panscale = 65536 / pan_maxrange;
}


void KOBO_sound::g_play(int wid, int x, int y, int vol, int pitch)
{
	int volume, vx, vy, pan;
	/* Calculate volume */
	x -= listener_x;
	y -= listener_y;
	if(wrap_x)
	{
		x += wrap_x / 2;
		while(x < 0)
			x += wrap_x;
		x %= wrap_x;
		x -= wrap_x / 2;
	}
	if(wrap_y)
	{
		y += wrap_y / 2;
		while(y < 0)
			y += wrap_y;
		y %= wrap_y;
		y -= wrap_y / 2;
	}

	/* Approximation of distance attenuation */
	vx = abs(x * scale);
	vy = abs(y * scale);
	if((vx | vy) & 0xffff0000)
		return;

	vx = (65536 - vx) >> 1;
	vy = (65536 - vy) >> 1;
	volume = vx * vy >> 14;
	volume = (volume>>1) * (volume>>1) >> 14;

	pan = x * panscale;
	if(pan < -65536)
		pan = -65536;
	else if(pan > 65536)
		pan = 65536;

	audio_channel_control(SOUND_GROUP_SFX, AVT_FUTURE, ACC_PATCH, wid);
	audio_channel_control(SOUND_GROUP_SFX, AVT_FUTURE, ACC_PAN, pan);
	audio_channel_control(SOUND_GROUP_SFX, AVT_FUTURE, ACC_VOLUME, volume);
	audio_channel_play(SOUND_GROUP_SFX, sfx2d_tag, pitch, vol);
	sfx2d_tag = (sfx2d_tag + 1) & 0xffff;
}


void KOBO_sound::g_play0(int wid, int vol, int pitch)
{
	audio_channel_control(SOUND_GROUP_SFX, AVT_FUTURE, ACC_PATCH, wid);
	audio_channel_control(SOUND_GROUP_SFX, AVT_FUTURE, ACC_PAN, 0);
	audio_channel_control(SOUND_GROUP_SFX, AVT_FUTURE, ACC_VOLUME, 65536);
	audio_channel_play(SOUND_GROUP_SFX, sfx2d_tag, 60<<16, vol);
	sfx2d_tag = (sfx2d_tag + 1) & 0xffff;
}


void KOBO_sound::g_player_fire()
{
	if(firing)
		g_play0(SOUND_SHOT, (prefs->cannonloud << 13) / 100);
	else
	{
		firing = 1;
		g_play0(SOUND_SHOT_START, 20000);
	}
	overheat = 0;
}


void KOBO_sound::g_player_fire_off()
{
	if(firing)
	{
		firing = 0;
		g_play0(SOUND_SHOT_END, 20000);
	}
}


void KOBO_sound::g_player_overheat(int classic)
{
	if(overheat)
		return;
	overheat = 1;
	g_play0(SOUND_SHOT_OVERHEAT, (prefs->cannonloud << 13) / 75);
	if(!classic)
		g_play(SOUND_OVERHEAT, listener_x + 100, listener_y,
				(prefs->overheatloud << 14) / 100);
}


void KOBO_sound::g_player_damage()
{
	int p0 = (60<<16) + (pubrand.get(9) << 8);
	g_play(SOUND_DAMAGE, listener_x - 100, listener_y,
			65536, p0 + 3000);
	g_play(SOUND_DAMAGE, listener_x + 100, listener_y,
			65536, p0 - 3000);
}


void KOBO_sound::g_player_explo_start()
{
	g_player_damage();
	g_play(SOUND_EXPLO_PLAYER, listener_x - 100, listener_y,
			65536, (55<<16) + 4000);
	g_play(SOUND_EXPLO_PLAYER, listener_x + 100, listener_y,
			65536, (55<<16) - 4000);
}


void KOBO_sound::g_bolt_hit(int x, int y)
{
	g_play(SOUND_METALLIC1 + (pubrand.get() % 3),
			CS2PIXEL(x), CS2PIXEL(y),
			12000);
}


void KOBO_sound::g_bolt_hit_rock(int x, int y)
{
	g_play(SOUND_HIT1 + (pubrand.get() % 3),
			CS2PIXEL(x), CS2PIXEL(y),
			20000);
}


void KOBO_sound::g_base_node_explo(int x, int y)
{
	g_play(pubrand.get(8) > 128  ? SOUND_EXPLO_NODE1 : SOUND_EXPLO_NODE2,
			CS2PIXEL(x), CS2PIXEL(y));
	rumble = 0;
}


void KOBO_sound::g_base_core_explo(int x, int y)
{
	x = CS2PIXEL(x);
	y = CS2PIXEL(y);
	g_play(pubrand.get(8) > 128  ? SOUND_EXPLO_NODE1 : SOUND_EXPLO_NODE2,
			x, y, 100000, 62<<16);
	g_play(SOUND_FINE, x + 30, y, 65536, (60<<16) + 4000);
	g_play(SOUND_FINE, x - 30, y, 65536, (60<<16) - 4000);
	rumble = 0;
}


void KOBO_sound::g_pipe_rumble(int x, int y)
{
	if(rumble < 4 + pubrand.get(3))
		return;
	g_play(pubrand.get(8) > 128  ? SOUND_RUMBLE_NODE1 : SOUND_RUMBLE_NODE2,
			CS2PIXEL(x), CS2PIXEL(y));
	rumble = 0;
}


void KOBO_sound::g_enemy_explo(int x, int y)
{
	g_play(SOUND_EXPLO_ENEMY1 + (pubrand.get() % 2),
			CS2PIXEL(x), CS2PIXEL(y), 40000);
}


void KOBO_sound::g_rock_explo(int x, int y)
{
	g_play(SOUND_EXPLO_ROCK, CS2PIXEL(x), CS2PIXEL(y), 40000);
}


void KOBO_sound::g_ring_explo(int x, int y)
{
	g_play(SOUND_EXPLO_RING1 + (pubrand.get() % 3),
			CS2PIXEL(x), CS2PIXEL(y), 32768);
}


void KOBO_sound::g_bomb_deto(int x, int y)
{
	g_play(SOUND_BOMB_DETO, CS2PIXEL(x), CS2PIXEL(y), 40000);
}


void KOBO_sound::g_launch_ring(int x, int y)
{
	g_play(SOUND_RING, CS2PIXEL(x), CS2PIXEL(y));
}


void KOBO_sound::g_launch_beam(int x, int y)
{
	g_play(SOUND_BEAM, CS2PIXEL(x), CS2PIXEL(y));
}


void KOBO_sound::g_launch_bomb(int x, int y)
{
	g_play(SOUND_LAUNCH2, CS2PIXEL(x), CS2PIXEL(y));
}


void KOBO_sound::g_launch(int x, int y)
{
	g_play(SOUND_LAUNCH, CS2PIXEL(x), CS2PIXEL(y));
}


void KOBO_sound::g_m_launch_ring(int x, int y)
{
	g_play(SOUND_RING, CS2PIXEL(x), CS2PIXEL(y));
	g_play(SOUND_ENEMYM, CS2PIXEL(x), CS2PIXEL(y));
}


void KOBO_sound::g_m_launch_beam(int x, int y)
{
	g_play(SOUND_BEAM, CS2PIXEL(x), CS2PIXEL(y));
	g_play(SOUND_ENEMYM, CS2PIXEL(x), CS2PIXEL(y));
}


void KOBO_sound::g_m_launch_bomb(int x, int y)
{
	g_play(SOUND_LAUNCH2, CS2PIXEL(x), CS2PIXEL(y));
	g_play(SOUND_ENEMYM, CS2PIXEL(x), CS2PIXEL(y));
}


void KOBO_sound::g_m_launch(int x, int y)
{
	g_play(SOUND_LAUNCH, CS2PIXEL(x), CS2PIXEL(y));
	g_play(SOUND_ENEMYM, CS2PIXEL(x), CS2PIXEL(y));
}


/*--------------------------------------------------
	UI sound effects
--------------------------------------------------*/

void KOBO_sound::ui_music(int wid)
{
// KLUDGE UNTIL MIDI SONGS AND FX CONTROL IS FIXED!
	audio_bus_control(0, 1, ABC_FX_TYPE, AFX_NONE);
	audio_bus_controlf(0, 0, ABC_SEND_MASTER, 1.0);
	audio_bus_controlf(0, 0, ABC_SEND_BUS_7, 0.5);
// KLUDGE UNTIL MIDI SONGS AND FX CONTROL IS FIXED!
	audio_channel_control(SOUND_GROUP_UIMUSIC, AVT_FUTURE, ACC_PATCH, wid);
	audio_channel_play(SOUND_GROUP_UIMUSIC, 0, 60<<16, 65536);
}


void KOBO_sound::ui_noise(int n)
{
	if(n < 0)
	{
		sound.sfx_volume(0.0f);
		audio_channel_control(SOUND_GROUP_UI,
				AVT_FUTURE, ACC_PATCH, SOUND_BZZZT);
		audio_channel_play(SOUND_GROUP_UI, 0, 60<<16,
				10000 + pubrand.get(14));
		return;
	}
	if(n && pubrand.get(1))
	{
		sound.sfx_volume(0.0f);
		audio_channel_control(SOUND_GROUP_UI,
				AVT_FUTURE, ACC_PATCH, SOUND_BZZZT);
		audio_channel_play(SOUND_GROUP_UI, 0, 60<<16,
				20000 + pubrand.get(14));
	}
	else
		sound.sfx_volume(1.0f);
}


void KOBO_sound::ui_ok()
{
	play(SOUND_EXPLO_NODE1, 32768);
}


void KOBO_sound::ui_cancel()
{
	play(SOUND_CANCEL, 20000);
}


void KOBO_sound::ui_move()
{
	play(SOUND_MOVE, 20000);
}


void KOBO_sound::ui_tick()
{
	play(SOUND_METALLIC1, 15000);
}


void KOBO_sound::ui_error()
{
	play(SOUND_OVERHEAT, 20000);
}


void KOBO_sound::ui_play()
{
	play(SOUND_PLAY, 32768, (50<<16) + 2000, -40000);
	play(SOUND_PLAY, 32768, (50<<16) - 2000, 40000);
}


void KOBO_sound::ui_pause()
{
	play(SOUND_PAUSE, 32768, (56<<16) + 2000, -40000);
	play(SOUND_PAUSE, 32768, (56<<16) - 2000, 40000);
}


void KOBO_sound::ui_ready()
{
	play(SOUND_READY, 15000, (60<<16) + 3000, -40000);
	play(SOUND_READY, 15000, (60<<16) - 3000, 40000);
}


void KOBO_sound::ui_countdown(int remain)
{
	play(SOUND_TICK, 20000, (65 - remain)<<16);
}


void KOBO_sound::ui_oneup()
{
	play(SOUND_ONEUP, 32768, (60<<16) + 5000, -40000);
	play(SOUND_ONEUP, 32768, (60<<16) - 5000, 40000);
}


void KOBO_sound::ui_gameover()
{
	play(SOUND_GAMEOVER);
}
