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

GRAOUMF TRACKER 2

Copyright (c) 1996 - 2002 Laurent de Soras

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.

Contact the author : laurent@ohmforce.com
More information about this license : http://www.gnu.org/licenses/gpl.html

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



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

#include	<stdio.h>
#include	<math.h>

#include	"archi.h"
#include	"base.h"
#include	"base_ct.h"
#include	"Envelope.h"
#include	"log.h"
#include	"WaveForm.h"
#include	"WaveForm.hpp"



/*\\\ CONSTANTES PUBLIQUES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

const Envelope_CONF	Envelope::CONFIG [Envelope_NBR_TYPES] =
{
	{ "Volume", true, false, true, 0, 0x1000, 0x0000, 0x7FFF },
	{ "Tone", false, true, true, 2, 0, -9999, 9999 },		// Centiemes de demi-tons
	{ "Panning", true, true, false, 0, 0x0000, -0x0080, 0x007F },
	{ "Cut-off", false, true, true, 2, 0, -9999, 9999 },	// Centiemes de demi-tons
	{ "Reso", false, true, true, 2, 0, -9999, 9999 }		// Centiemes de decibels
};



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



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



/*\\\ DEFINITION DES VARIABLES DE CLASSE PUBLIQUES \\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ DEFINITION DES VARIABLES DE CLASSE PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ METHODES PUBLIQUES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*==========================================================================*/
/*      Nom: (constructeur)                                                 */
/*      Description: Initialise                                             */
/*      Parametres en entree:                                               */
/*        - number: numero de l'enveloppe.                                  */
/*        - type: type de l'enveloppe.                                      */
/*==========================================================================*/

Envelope::Envelope (int number, int type)
{
	_number = number;
	_type = type;

	init ();
}



/*==========================================================================*/
/*      Nom: (destructeur)                                                  */
/*      Description: Detruit                                                */
/*==========================================================================*/

Envelope::~Envelope (void)
{
	restore ();
}



/*==========================================================================*/
/*      Nom: check_ok                                                       */
/*      Description: Verifie l'intergrite de l'objet.                       */
/*      Retour: 0 si l'objet parait correct.                                */
/*==========================================================================*/

signed int	Envelope::check_ok (void) const
{
	if (this == NULL)
	{
		LOG_printf ("Envelope::check_ok: Error: \"this\" pointer is NULL.\n");
		return (-1);
	}

	return (0);
}



/*==========================================================================*/
/*      Nom: self_display                                                   */
/*      Description: Affiche "textuellement" le contenu courant de l'objet. */
/*==========================================================================*/

void	Envelope::self_display (void) const
{

	/*** A faire ***/

}



WORD	Envelope::get_flags (void) const
{
	return (_flags);
}



void	Envelope::set_flags (WORD flags)
{
	_flags = flags;
}



int	Envelope::get_nbr_points (void) const
{
	return (_nbr_points);
}



void	Envelope::set_nbr_points (int nbr_points)
{
	_nbr_points = nbr_points;
}



Envelope_POINT	Envelope::get_point (int pos) const
{
	return (_point [pos]);
}



void	Envelope::set_point (int pos, const Envelope_POINT &point)
{
	_point [pos] = point;
}



Envelope_LOOP	Envelope::get_loop (int type) const
{
	return (_loop [type]);
}



void	Envelope::set_loop (int type, const Envelope_LOOP &loop)
{
	Envelope_LOOP	temp;

	/* Petite verification */
	temp = loop;
	temp.end = MIN (temp.end, _nbr_points - 1);
	temp.end = MAX (temp.end, 0);
	temp.start = MIN (temp.start, temp.end);
	temp.start = MAX (temp.start, 0);

	_loop [type] = temp;
}



UWORD	Envelope::get_lfo_speed (void) const
{
	return (_lfo_speed);
}



void	Envelope::set_lfo_speed (UWORD val)
{
	_lfo_speed = val;
}



UWORD	Envelope::get_lfo_depth (void) const
{
	return (_lfo_depth);
}



void	Envelope::set_lfo_depth (UWORD val)
{
	_lfo_depth = val;
}



UWORD	Envelope::get_lfo_sweep (void) const
{
	return (_lfo_sweep);
}



void	Envelope::set_lfo_sweep (UWORD val)
{
	_lfo_sweep = val;
}



int	Envelope::get_lfo_waveform (void) const
{
	return (_lfo_waveform);
}



void	Envelope::set_lfo_waveform (int val)
{
	_lfo_waveform = val;
}



UWORD	Envelope::get_fadeout (void) const
{
	return (_fadeout);
}



void	Envelope::set_fadeout (UWORD val)
{
	_fadeout = val;
}



void	Envelope::start (Envelope_PROCESS &proc)
{
	QWORD		sweep_inc;

	proc.tick_cnt = -1;
	proc.cur_point = 0;
	proc.cur_val = CONFIG [_type].std_val;
	proc.val_inc = 0;
	proc.cur_val_frac = 0;
	proc.val_inc_frac = 0;
	proc.fade_val = 0x10000L;
	proc.sweep_val_frac = 0;
	if (_lfo_sweep == 0)
	{
		proc.sweep_val = _lfo_depth;
		proc.sweep_inc = 0;
		proc.sweep_inc_frac = 0;
	}
	else
	{
		sweep_inc = ((QWORD)_lfo_depth << 32) / _lfo_sweep;
		proc.sweep_val = 0;
		proc.sweep_inc = (LWORD)(sweep_inc >> 32);
		proc.sweep_inc_frac = (ULWORD)sweep_inc;
	}
	proc.lfo_pos = 0;
	proc.loop_type = -1;
	for (int cnt = 0; cnt < Envelope_NBR_LOOPS; cnt ++)
	{
		proc.loop_cnt [cnt] = _loop [cnt].nbr_loops;
	}
	proc.final_val = CONFIG [_type].std_val;
	proc.one_point_sustain_flag = 0;
	if (   (_flags & Envelope_FLAG_SUS_LOOP) != 0
	    && proc.loop_cnt [Envelope_LOOP_SUSTAIN] > 0
	    && proc.cur_point == _loop [Envelope_LOOP_SUSTAIN].start
	    && proc.cur_point == _loop [Envelope_LOOP_SUSTAIN].end)
	{
		proc.one_point_sustain_flag = 1;
	}
	proc.key_off_flag = 0;
}



void	Envelope::process_tick (Envelope_PROCESS &proc)
{
	signed long	cur_val;
	SQWORD	inc_tot;

	/* Initialise le point s'il est nouveau et
		qu'il est bien dans la liste. */
	if (   proc.tick_cnt < 0
	    && proc.cur_point < _nbr_points)
	{
		proc.tick_cnt = 0;
		proc.cur_val = _point [proc.cur_point].val;
		proc.cur_val_frac = 0;

		if (   proc.cur_point < _nbr_points - 1
			 && ! proc.one_point_sustain_flag)
		{
			inc_tot =   ((SQWORD) (_point [proc.cur_point + 1].val - proc.cur_val) << 32)
			          / _point [proc.cur_point].time;
			proc.val_inc = (SWORD) (inc_tot >> 32);
			proc.val_inc_frac = (ULWORD) inc_tot;
		}
		else
		{
			proc.val_inc = 0;
			proc.val_inc_frac = 0;
		}
	}

/*______________________________________________
 *
 * Calcul de la valeur courante
 *______________________________________________
 */

	cur_val = proc.cur_val;

	/* LFO */
	if (_flags & Envelope_FLAG_LFO)
	{
		cur_val += (signed long) floor
			(proc.sweep_val * WaveForm::base_ptr [_lfo_waveform]->get_point_fast
			(proc.lfo_pos >> (16 - WaveForm_BASE_LENGTH_P2)) + 0.5);
	}

	/* Fadeout */
	if (   proc.key_off_flag
	    && (_flags & Envelope_FLAG_FADEOUT) != 0)
	{
		cur_val = (cur_val * proc.fade_val) / 65536;
	}

	cur_val = MIN (cur_val, CONFIG [_type].max_val);
	proc.final_val = (SWORD) MAX (cur_val, CONFIG [_type].min_val);

/*______________________________________________
 *
 * Increment des valeurs pour le tick suivant
 *______________________________________________
 */

	/* Increment de la valeur */
	proc.cur_val = BASE_ADDX (ULWORD,
	                          proc.cur_val, proc.cur_val_frac,
	                          proc.val_inc, proc.val_inc_frac);
	proc.cur_val_frac += proc.val_inc_frac;

	/* Increment du LFO */
	proc.lfo_pos += _lfo_speed;
	proc.sweep_val = BASE_ADDX (ULWORD,
	                            proc.sweep_val, proc.sweep_val_frac,
	                            proc.sweep_inc, proc.sweep_inc_frac);
	proc.sweep_val_frac += proc.sweep_inc_frac;
	proc.sweep_val = MIN (proc.sweep_val, _lfo_depth);

	/* Increment du fadeout */
	if (proc.key_off_flag)
	{
		if (_fadeout & 0x8000)
		{

			/*** A faire ***/

		}
		else
		{
			proc.fade_val -= _fadeout;
			proc.fade_val = MAX (proc.fade_val, 0);
		}
	}

/*______________________________________________
 *
 * Changements de point
 *______________________________________________
 */

	/* Si le point n'est pas le dernier de la liste,
		on incremente le compteur de ticks. */
	if (proc.cur_point < _nbr_points - 1)
	{
		proc.tick_cnt ++;
	}

	/* On a fini le segment courant ? */
	if (   proc.tick_cnt >= _point [proc.cur_point].time
	    || proc.one_point_sustain_flag)
	{
		/* Point suivant */
		if (proc.one_point_sustain_flag == 0)
		{
			proc.cur_point ++;
		}
		proc.one_point_sustain_flag = 0;

		/* On est en mode key off */
		if (proc.key_off_flag)
		{
			/* Boucle d'enveloppe presente et on depasse */
			if (   (_flags & Envelope_FLAG_ENV_LOOP) != 0
			    && proc.loop_cnt [Envelope_LOOP_NORMAL] > 0)
			{
				proc.cur_point = _loop [Envelope_LOOP_NORMAL].start;
				proc.loop_cnt [Envelope_LOOP_NORMAL] --;
				if (   _loop [Envelope_LOOP_NORMAL].start
				    == _loop [Envelope_LOOP_NORMAL].end)
				{
					proc.one_point_sustain_flag = 1;
				}
			}
		}

		/* Mode key on */
		else
		{
			/* S'il y a une boucle de sustain, on avance jusqu'a
				cette boucle meme s'il y a une boucle d'enveloppe. */
			if (   (_flags & Envelope_FLAG_SUS_LOOP) != 0
			    && proc.loop_cnt [Envelope_LOOP_SUSTAIN] > 0)
			{
				if (proc.cur_point >= _loop [Envelope_LOOP_SUSTAIN].end)
				{
					proc.cur_point = _loop [Envelope_LOOP_SUSTAIN].start;
					proc.loop_cnt [Envelope_LOOP_SUSTAIN] --;
					if (   _loop [Envelope_LOOP_SUSTAIN].start
					    == _loop [Envelope_LOOP_SUSTAIN].end)
					{
						proc.one_point_sustain_flag = 1;
					}
				}
			}

			/* Boucle d'enveloppe presente et on depasse */
			else if (   (_flags & Envelope_FLAG_ENV_LOOP) != 0
			         && proc.loop_cnt [Envelope_LOOP_NORMAL] > 0
			         && proc.cur_point >= _loop [Envelope_LOOP_NORMAL].end)
			{
				proc.cur_point = _loop [Envelope_LOOP_NORMAL].start;
				proc.loop_cnt [Envelope_LOOP_NORMAL] --;
				if (   _loop [Envelope_LOOP_NORMAL].start
				    == _loop [Envelope_LOOP_NORMAL].end)
				{
					proc.one_point_sustain_flag = 1;
				}
			}
		}

		proc.tick_cnt = -1;
	}
}



signed int	Envelope::kill_envelope (void)
{
	restore ();

	return (init ());
}



/*\\\ METHODES PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



signed int	Envelope::init (void)
{
	_nbr_points = 0;
	_lfo_speed = 0;
	_lfo_depth = 0;
	_lfo_sweep = 0;
	_lfo_waveform = WaveForm_BASE_SIN;
	_fadeout = 0;
	_flags = 0;

	for (int cnt = 0; cnt < Envelope_NBR_LOOPS; cnt ++)
	{
		_loop [cnt].start = 0;
		_loop [cnt].end = 0;
		_loop [cnt].nbr_loops = Envelope_LOOP_INFINITE;
	}

	return (0);
}



void	Envelope::restore (void)
{
	/* Rien */
}



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



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