/*****************************************************************************

        GRAOUMF TRACKER 2
        Author: Laurent de Soras, 1996-2016

--- Legal stuff ---

This program is free software. It comes without any warranty, to
the extent permitted by applicable law. You can redistribute it
and/or modify it under the terms of the Do What The Fuck You Want
To Public License, Version 2, as published by Sam Hocevar. See
http://sam.zoy.org/wtfpl/COPYING for more details.

*****************************************************************************/



/*\\\ FICHIERS INCLUDE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

#include	<string.h>
#include	<math.h>

#include	"archi.h"
#include	"base.h"
#include	"filter.h"
#include	"FxPreset.h"
#include	"mix_fx.h"
#include	"mixer.h"
#include	"player.h"



/*\\\ CONSTANTES PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ TYPES & STRUCTURES PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ PROTOTYPES DES FONCTIONS PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

void	MIXFX_none (Player::TrackInfo &track_info, const float *s_buffer_ptr, float *d_buffer_ptr);
void	MIXFX_bypass (Player::TrackInfo &track_info, const float *s_buffer_ptr, float *d_buffer_ptr);

void	MIXFX_delay (Player::TrackInfo &track_info, const float *s_buffer_ptr, float *d_buffer_ptr);

void	MIXFX_resfilt (Player::TrackInfo &track_info, const float *s_buffer_ptr, float *d_buffer_ptr);

void	MIXFX_disto (Player::TrackInfo &track_info, const float *s_buffer_ptr, float *d_buffer_ptr);
inline float	MIXFX_disto_boost (float val, double g, double k);
inline float	MIXFX_disto_punch (float val, double g, double k, double p, double p2);



/*\\\ VARIABLES EXTERNES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ VARIABLES PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ FONCTIONS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



void	MIXFX_do_fx (Player::TrackInfo &track_info, const float *s_buffer_ptr, float *d_buffer_ptr)
{
	/* Effet en mode bypass */
	if (track_info.mix.fx.bypass_flag)
	{
		MIXFX_bypass (track_info, s_buffer_ptr, d_buffer_ptr);
	}

	/* Effet mal initialise */
	else if (track_info.mix.fx.bad_init_flag)
	{
		MIXFX_none (track_info, s_buffer_ptr, d_buffer_ptr);
	}

	/* Effets en mode normal */
	else
	{
		switch (track_info.mix.fx.effect)
		{
		default:
			MIXFX_none (track_info, s_buffer_ptr, d_buffer_ptr);
			break;

		case	FxPreset_TYPE_DELAY:
			MIXFX_delay (track_info, s_buffer_ptr, d_buffer_ptr);
			break;

		case	FxPreset_TYPE_RESFILT:
			MIXFX_resfilt (track_info, s_buffer_ptr, d_buffer_ptr);
			break;

		case	FxPreset_TYPE_DISTO:
			MIXFX_disto (track_info, s_buffer_ptr, d_buffer_ptr);
			break;
		}
	}
}



/****************************************************************************/
/*                                                                          */
/*      AUCUN EFFET                                                         */
/*                                                                          */
/****************************************************************************/



void	MIXFX_none (Player::TrackInfo &track_info, const float *s_buffer_ptr, float *d_buffer_ptr)
{
	memset (d_buffer_ptr, 0, MIX_frame_length * sizeof (*d_buffer_ptr) * track_info.stereo);
}



void	MIXFX_bypass (Player::TrackInfo &track_info, const float *s_buffer_ptr, float *d_buffer_ptr)
{
	long		cnt;

	if (track_info.stereo == 2)
	{
		memcpy (d_buffer_ptr, s_buffer_ptr, MIX_frame_length * sizeof (*d_buffer_ptr) * 2);
	}
	else
	{
		cnt = MIX_frame_length;
		do
		{
			cnt --;
			d_buffer_ptr [cnt] =   s_buffer_ptr [cnt * 2]
			                     + s_buffer_ptr [cnt * 2 + 1];
		}
		while (cnt > 0);
	}
}



/****************************************************************************/
/*                                                                          */
/*      DELAY                                                               */
/*                                                                          */
/****************************************************************************/



void	MIXFX_delay (Player::TrackInfo &track_info, const float *s_buffer_ptr, float *d_buffer_ptr)
{
	float *	delay_buffer_ptr;
	float *	write_ptr;
	float *	read_ptr;
	LWORD		delay_buffer_len;
	LWORD		write_pos;
	LWORD		read_pos;
	LWORD		work_len;
	LWORD		remaining_len;
	float		feedback;
	float		sample;

	feedback = (track_info.mix.fx.internal.delay.feedback) * (1.0f / 4294967296.0f);
	delay_buffer_len = track_info.mix.fx.internal.delay.buf_len;
	delay_buffer_ptr = track_info.mix.fx.internal.delay.buf_ptr;
	write_pos = track_info.mix.fx.internal.delay.write_pos;
	read_pos = write_pos + delay_buffer_len - track_info.mix.fx.internal.delay.delay;
	remaining_len = MIX_frame_length;

	do
	{
		if (read_pos >= delay_buffer_len)
		{
			read_pos -= delay_buffer_len;
		}

		/* Recherche qui est le plus pres de la fin du buffer (pointeur de lecture
			ou d'ecriture) */
		if (read_pos > write_pos)
		{
			work_len = delay_buffer_len - read_pos;
		}
		else
		{
			work_len = delay_buffer_len - write_pos;
		}

		work_len = MIN (work_len, remaining_len);
		remaining_len -= work_len;

		/* Applique le delay sur la longueur trouvee */
		read_ptr = delay_buffer_ptr + read_pos;
		write_ptr = delay_buffer_ptr + write_pos;
		write_pos += work_len;
		read_pos += work_len;
		do
		{
			work_len --;
			sample = *read_ptr++;
			*d_buffer_ptr++ = sample;
			sample += *s_buffer_ptr++;
			sample += *s_buffer_ptr++;
			*write_ptr++ = sample * feedback;
		}
		while (work_len > 0);

		if (write_pos >= delay_buffer_len)
		{
			write_pos -= delay_buffer_len;
		}
	}
	while (remaining_len > 0);

	track_info.mix.fx.internal.delay.write_pos = write_pos;
}



/****************************************************************************/
/*                                                                          */
/*      FILTRE RESONNANT                                                    */
/*                                                                          */
/****************************************************************************/



void	MIXFX_resfilt (Player::TrackInfo &track_info, const float *s_buffer_ptr, float *d_buffer_ptr)
{
	double	dummy;
	int		cell_cnt;
	int		order_cnt;
	int		biq_cnt;
	int		nbr_cells;
	int		nbr_biquads;
	int		spl_cnt;
	int		src_stereo;
	float *	buffer_ptr;
	float *	cur_dest_ptr;
	const float *	cur_src_ptr;

	buffer_ptr = 0;
	nbr_biquads = track_info.mix.fx.conf.resfilt.nbr_biquads;
	nbr_cells = 1;
	if (track_info.mix.fx.conf.resfilt.cell_flag)
	{
		nbr_cells += track_info.mix.fx.conf.resfilt.nbr_cells;
	}

	biq_cnt = 0;
	cur_dest_ptr = d_buffer_ptr;
	cur_src_ptr = s_buffer_ptr;
	src_stereo = 2;
	for (cell_cnt = 0; cell_cnt < nbr_cells; cell_cnt ++)
	{
		if (   cell_cnt > 0
		    && track_info.mix.fx.internal.resfilt.mix_cell_flag)
		{
			if (cell_cnt == 1)
			{
				buffer_ptr = new float [MIX_frame_length * track_info.stereo];
			}
			src_stereo = 2;
			cur_src_ptr = s_buffer_ptr;
			cur_dest_ptr = buffer_ptr;
		}

		for (order_cnt = 0; order_cnt < nbr_biquads; order_cnt ++)
		{
			FILT_do_iir_filtering (
				cur_src_ptr,
				cur_dest_ptr,
				src_stereo,
				track_info.stereo,
				track_info.mix.fx.internal.resfilt.biquad [biq_cnt].x_coef,
				track_info.mix.fx.internal.resfilt.biquad [biq_cnt].y_coef,
				track_info.mix.fx.internal.resfilt.biquad [biq_cnt].nbr_coef,
				track_info.mix.fx.internal.resfilt.biquad [biq_cnt].x_buffer,
				track_info.mix.fx.internal.resfilt.biquad [biq_cnt].y_buffer,
				track_info.mix.fx.internal.resfilt.biquad [biq_cnt].buffer_pos,
				Player::FX_RESFILT_INT_BUF_LEN,
				MIX_frame_length
			);
			biq_cnt ++;
			cur_src_ptr = cur_dest_ptr;
			src_stereo = track_info.stereo;
		}

		if (   cell_cnt > 0
		    && track_info.mix.fx.internal.resfilt.mix_cell_flag)
		{
			spl_cnt = MIX_frame_length * track_info.stereo;
			do
			{
				spl_cnt --;
				d_buffer_ptr [spl_cnt] += buffer_ptr [spl_cnt];
			}
			while (spl_cnt > 0);
		}
	}

	if (buffer_ptr != 0)
	{
		delete [] buffer_ptr;
		buffer_ptr = 0;
	}

	/* Mise a jour de la phase de l'oscillateur */
	if (track_info.mix.fx.conf.resfilt.lfo_flag)
	{
		track_info.mix.fx.conf.resfilt.lfo_phase +=
			track_info.mix.fx.conf.resfilt.lfo_freq * MIX_frame_length / MIX_replay_freq;
		track_info.mix.fx.conf.resfilt.lfo_phase =
			modf (track_info.mix.fx.conf.resfilt.lfo_phase, &dummy);

		track_info.mix.fx.update_flag = true;
	}

}



/****************************************************************************/
/*                                                                          */
/*      DISTORSION                                                          */
/*                                                                          */
/****************************************************************************/



void	MIXFX_disto (Player::TrackInfo &track_info, const float *s_buffer_ptr, float *d_buffer_ptr)
{
	double		g;
	double		k;
	double		p;
	long			cur_pos;

	cur_pos = MIX_frame_length * track_info.stereo;

	switch (track_info.mix.fx.conf.disto.type)
	{

/*______________________________________________
 *
 * Mode Boost (log)
 *______________________________________________
 */

	case	FxPreset_DISTO_TYPE_BOOST:

		g = track_info.mix.fx.internal.disto.g;
		k = track_info.mix.fx.internal.disto.k;

		if (track_info.stereo == 2)
		{
			do
			{
				cur_pos --;
				d_buffer_ptr [cur_pos] = MIXFX_disto_boost (s_buffer_ptr [cur_pos], g, k);
			}
			while (cur_pos > 0);
		}

		else
		{
			do
			{
				cur_pos --;
				d_buffer_ptr [cur_pos] = MIXFX_disto_boost (  s_buffer_ptr [cur_pos * 2]
				                                            + s_buffer_ptr [cur_pos * 2 + 1], g, k);
			}
			while (cur_pos > 0);
		}

		break;

/*______________________________________________
 *
 * Mode Punch (sin)
 *______________________________________________
 */

	case	FxPreset_DISTO_TYPE_PUNCH:

		g = track_info.mix.fx.internal.disto.g;
		k = track_info.mix.fx.internal.disto.k;
		p = track_info.mix.fx.internal.disto.p;

		if (track_info.stereo == 2)
		{
			do
			{
				cur_pos --;
				d_buffer_ptr [cur_pos] = MIXFX_disto_punch (s_buffer_ptr [cur_pos],
				                                            g, k, p, p * (1.0/0x800000));
			}
			while (cur_pos > 0);
		}

		else
		{
			do
			{
				cur_pos --;
				d_buffer_ptr [cur_pos] = MIXFX_disto_punch (  s_buffer_ptr [cur_pos * 2]
				                                            + s_buffer_ptr [cur_pos * 2 + 1],
				                                            g, k, p, p * (1.0/0x800000));
			}
			while (cur_pos > 0);
		}

		break;
	}
}



inline float	MIXFX_disto_boost (float val, double g, double k)
{
	if (val < 0)
	{
		val = float (-k * log (1.0 + log (1.0 - val * g)));
	}
	else
	{
		val = float (k * log (1.0 + log (1.0 + val * g)));
	}

	return (val);
}



inline float	MIXFX_disto_punch (float val, double g, double k, double p, double p2)
{
	double		x = 1.0 - fabs (val) * p2;
	if (x < 0)
	{
		x = 0;
	}
	x = x * k * sin (val * g) + val * p;
	x = MIN (x, +8388608.0);
	x = MAX (x, -8388608.0);

	return (float (x));
}



/****************************************************************************/
/*                                                                          */
/*                                                                          */
/*                                                                          */
/****************************************************************************/



/*==========================================================================*/
/*      Nom:                                                                */
/*      Description:                                                        */
/*      Parametres en entree:                                               */
/*      Parametres en sortie:                                               */
/*      Parametres en entree/sortie:                                        */
/*      Retour:                                                             */
/*==========================================================================*/
