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

        GainSmooth.cpp
        Copyright (c) 2012 Laurent de Soras

*Tab=3***********************************************************************/



#if defined (_MSC_VER)
	#pragma warning (1 : 4130 4223 4705 4706)
	#pragma warning (4 : 4355 4786 4800)
#endif



/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

#include	"AssignSpl.h"
#include	"GainSmooth.h"

#include	<algorithm>

#include	<cassert>



/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



GainSmooth::GainSmooth ()
:	_vol_old (1)
,	_vol_cur (1)
,	_vol_target (1)
,	_ramp_pos (-1)
,	_initial_flag (false)
{
	// Nothing
}



void	GainSmooth::clear_buffers ()
{
	_vol_cur = _vol_target;
	_ramp_pos = -1;
}



void	GainSmooth::set_initial_flag (bool initial_flag)
{
	_initial_flag = initial_flag;
}



void	GainSmooth::set (float vol)
{
	if (_initial_flag)
	{
		force (vol);
	}
	else if (vol != _vol_target)
	{
		_vol_old = _vol_cur;
		_vol_target = vol;
		_ramp_pos = 0;
	}
}



void	GainSmooth::force (float vol)
{
	_vol_target = vol;
	clear_buffers ();
}



bool	GainSmooth::is_ramping () const
{
	return (_ramp_pos >= 0);
}



float	GainSmooth::get_vol_cur () const
{
	return (_vol_cur);
}



float	GainSmooth::get_vol_target () const
{
	return (_vol_target);
}



void	GainSmooth::mix (float dest_ptr [], const float src_ptr [], long nbr_spl, int stride_dst, int stride_src)
{
	process <AssignSpl::Mix> (dest_ptr, src_ptr, nbr_spl, stride_dst, stride_src);
}



void	GainSmooth::replace (float dest_ptr [], const float src_ptr [], long nbr_spl, int stride_dst, int stride_src)
{
	process <AssignSpl::Replace> (dest_ptr, src_ptr, nbr_spl, stride_dst, stride_src);
}



void	GainSmooth::mix_or_replace (float dest_ptr [], const float src_ptr [], long nbr_spl, int stride_dst, int stride_src, bool repl_flag)
{
	if (repl_flag)
	{
		replace (dest_ptr, src_ptr, nbr_spl, stride_dst, stride_src);
	}
	else
	{
		mix (dest_ptr, src_ptr, nbr_spl, stride_dst, stride_src);
	}
}

void	GainSmooth::skip (long nbr_spl)
{
	assert (nbr_spl > 0);

	if (is_ramping ())
	{
		_ramp_pos += nbr_spl;
		if (_ramp_pos >= FADE_LEN)
		{
			clear_buffers ();
		}
		else
		{
			const float		dif    = _vol_target - _vol_old;
			const float		step   = 1.0f / FADE_LEN;
			const float		ramp   = _ramp_pos * step;
			const float		interp = curve (ramp);
			_vol_cur = _vol_old + dif * interp;
		}
	}

	_initial_flag = false;
}



void	GainSmooth::scale (float scale)
{
	assert (scale != 0);

	_vol_old    *= scale;
	_vol_cur    *= scale;
	_vol_target *= scale;
}



void	GainSmooth::add (const GainSmooth &other)
{
	assert (&other != 0);
	assert (std::max (other._ramp_pos, 0L) == std::max (_ramp_pos, 0L));

	if (! is_ramping () && other.is_ramping ())
	{
		_vol_old    = _vol_cur + other._vol_old;
		_vol_target = _vol_cur + other._vol_target;
		_ramp_pos   = other._ramp_pos;
	}

	else
	{
		if (is_ramping () && ! other.is_ramping ())
		{
			_vol_old += other._vol_target;
		}
		else
		{
			_vol_old += other._vol_old;
		}

		_vol_target += other._vol_target;
	}

	_vol_cur += other._vol_cur;
}



/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



template <class T>
void	GainSmooth::process (float dest_ptr [], const float src_ptr [], long nbr_spl, int stride_dst, int stride_src)
{
	assert (dest_ptr != 0);
	assert (src_ptr != 0);
	assert (nbr_spl > 0);

	int				pos = 0;
	while (pos < nbr_spl)
	{
		if (is_ramping ())
		{
			const long		work_len =
				std::min (nbr_spl - pos, FADE_LEN - _ramp_pos);
			const float		dif  = _vol_target - _vol_old;
			const float		step = 1.0f / FADE_LEN;
			float				ramp = _ramp_pos * step;

			int				cnt = 0;
			float				vol;

			do
			{
				ramp += step;
				const float		interp = curve (ramp);
				vol = _vol_old + dif * interp;
				const float		x = src_ptr [pos * stride_src];
				const float		y = x * vol;
				T::assign (dest_ptr [pos * stride_dst], y);
				++ pos;
				++ cnt;
			}
			while (cnt < work_len);
			_ramp_pos += work_len;
			if (_ramp_pos < FADE_LEN)
			{
				_vol_cur = vol;
			}
			else
			{
				_vol_cur  = _vol_target;
				_ramp_pos = -1;
			}
		}

		else
		{
			// Neutral processing
			if (src_ptr == dest_ptr && ! T::_mix_flag && _vol_target == 1.0f)
			{
				pos = nbr_spl;
			}
			else
			{
				do
				{
					const float		x = src_ptr [pos * stride_src];
					const float		y = x * _vol_target;
					T::assign (dest_ptr [pos * stride_dst], y);
					++ pos;
				}
				while (pos < nbr_spl);
			}
		}
	}

	_initial_flag = false;
}



float	GainSmooth::curve (float x)
{
	assert (x >= 0);
	assert (x <= 1);

	return ((3 - 2 * x) * x * x);
}



/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
