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

        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.

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



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



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

#include	"Meter.h"

#include <algorithm>

#include	<cassert>
#include	<cmath>



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



Meter::Meter ()
:	_sample_freq (0)
,	_peak_max (0)
,	_peak_hold (0)
,	_hold_counter (0)
,  _rms_sq (0)
/*,	_dec ()*/
{
	set_sample_freq (44100);
}



void	Meter::set_sample_freq (double freq)
{
	assert (freq > 0);

	const double   hold_time_s    = 2.0;   // Hold time, in s
	const double   attack_time_s  = 0.025; // Attack time, in s
	const double   release_time_s = 0.25;  // Release time, in s

	_sample_freq = freq;

	for (int nbr_voices = 1; nbr_voices <= 2; ++nbr_voices)
	{
		const double   sf2 = _sample_freq * nbr_voices;
		Decay &        dec = _dec [nbr_voices - 1];

		dec._hold_time  = long (sf2 * hold_time_s + 0.5);
		dec._coef_r     = 1;
		const double   trsf = release_time_s * sf2;
		if (trsf > 1)
		{
			dec._coef_r  = 1.0 - exp (-1.0 / trsf);
		}
		dec._coef_r2    = 1;
		const double   tr2sf = release_time_s * sf2 * 0.5;
		if (tr2sf > 1)
		{
			dec._coef_r2 = 1.0 - exp (-1.0 / tr2sf);
		}
		dec._coef_a2    = 1;
		const double   ta2sf = attack_time_s * sf2 * 0.5;
		if (ta2sf > 1)
		{
			dec._coef_a2 = 1.0 - exp (-1.0 / ta2sf);
		}
	}
}



void	Meter::clear_buffers ()
{
	_peak_max     = 0;
	_peak_hold    = 0;
	_hold_counter = 0;
	_rms_sq       = 0;
}



void	Meter::process_block (const float data_ptr [], long buf_len, int nbr_voices)
{
	assert (data_ptr != 0);
	assert (buf_len > 0);
	assert (nbr_voices >= 1);
	assert (nbr_voices <= 2);

	const long     nbr_spl = buf_len * nbr_voices;
	const Decay &  dec     = _dec [nbr_voices - 1];

	for (int pos = 0; pos < nbr_spl; ++pos)
	{
		const float    x   = data_ptr [pos];
		const float    x_a = fabs (x);
		const float    x_2 = x * x;

		// Peak
		if (x_a > _peak_max)
		{
			_peak_max     = x_a;
		}

		if (x_a > _peak_hold)
		{
			_peak_hold    = x_a;
			_hold_counter = dec._hold_time;
		}
		else
		{
			if (_hold_counter > 0)
			{
				-- _hold_counter;
			}
			else
			{
				_peak_hold *= 1 - dec._coef_r;
			}
		}

		// RMS
		const double   delta = x_2 - _rms_sq;
		const double   coef  = (delta > 0) ? dec._coef_a2 : dec._coef_r2;
		_rms_sq += delta * coef;
	}

	fix_tiny_values ();
}



void	Meter::skip_block (long buf_len)
{
	assert (buf_len > 0);

	const Decay &  dec      = _dec [0];

	long           rem_len  = buf_len;
	const long     hold_len = std::min (rem_len, _hold_counter);
	if (hold_len > 0)
	{
		_hold_counter -= hold_len;
		rem_len       -= hold_len;
	}
	if (_hold_counter <= 0 && rem_len > 0)
	{
		const double   cumulated_coef = pow (1 - dec._coef_r, rem_len);
		_peak_hold *= cumulated_coef;
	}

	const double   cumulated_coef = pow (1 - dec._coef_r2, buf_len);
	_rms_sq *= cumulated_coef;

	fix_tiny_values ();
}



double	Meter::get_peak () const
{
	return (_peak_max);
}



double	Meter::get_peak_hold () const
{
	return (_peak_hold);
}



double	Meter::get_rms () const
{
	return (sqrt (_rms_sq));
}



void	Meter::clear_peak ()
{
	_peak_max = 0;
}



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



// Avoids entering the denormal zone with silent input.
void	Meter::fix_tiny_values ()
{
	const double   threshold = 1e-10;   // About -200 dB

	if (_rms_sq < threshold * threshold)
	{
		_rms_sq = 0;
	}

	if (_hold_counter == 0 && _peak_hold < threshold)
	{
		_peak_hold = 0;
	}
}



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



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