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

        Voice.cpp
        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 "base.h"
#include "d2d.h"
#include	"gtracker.h"	// For GTK_NBRSAMPLES_TOTAL
#include "memory.h"
#include	"mixer.h"
#include "Player.h"		// For Player::REF_PERIOD
#include	"ReconstructionFilter.h"
#include	"samp.h"
#include	"Voice.h"
#include	"WaveForm.h"

#include	<cassert>
#include	<cmath>
#include	<cstring>

namespace std { }



/* Frequence relative maximum pour le buffer lineaire de sample. Au dela, le
sample n'est pas mixe car le buffer lineaire prendrait trop de place. */
#define	MIX_LINEAR_BUFFER_MAX_FREQ_RATIO	16

/*
	Macro d'incrementation du pointeur source lors du reechantillonnage d'un
	sample. Parametres:
	SWORD/SBYTE		*in_ptr;
	ULWORD			pos_fraq;
	const int		freq_int;	Doit etre multiplie auparavant par stereo
	const ULWORD	freq_fraq;
	const int		stereo;
*/

#define	MIX_STEP(in_ptr,pos_frac,freq_int,freq_frac,stereo) \
	pos_frac += freq_frac;			\
	if (pos_frac < freq_frac)		\
	{										\
		in_ptr = in_ptr + stereo;	\
	}										\
	in_ptr = in_ptr + freq_int;



/* Buffer mono/stereo servant a stoquer le sample sous forme lineaire 16 bits
   avant reechantillonnage. La taille fait quatre fois la taille maximum d'une
	frame plus 64 samples de securite. */
SWORD	MIX_linear_sample_buffer [(MIX_MAX_FRAME_LENGTH * MIX_LINEAR_BUFFER_MAX_FREQ_RATIO + 64) * 2];



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



Voice::Voice ()
:	spl ()
,	filter ()
,	_env_set ()
,	_fadeout ()
,	_outperiod (Player::REF_PERIOD)
,	_outvol (0x1000)
,	_outpan (0x8000)
,	_cr ()
,	_gen_flag (false)
{
	// Nothing
}



void	Voice::copy_state (const Voice &other)
{
	assert (&other != 0);

	if (&other != this)
	{
		spl.copy_state (other.spl);

		filter     = other.filter;
		_env_set   = other._env_set;
		_fadeout   = other._fadeout;
		_outperiod = other._outperiod;
		_outvol    = other._outvol;
		_outpan    = other._outpan;
		_cr        = other._cr;
		_gen_flag  = other._gen_flag;
	}
}



// Sample 0 deactivates the voice immediately
void	Voice::start_sample (int spl_index)
{
	assert (spl_index >= 0);
	assert (spl_index <= GTK_NBRSAMPLES_TOTAL);

	// Sample
	spl.start (spl_index);

	// Resonant filter
	filter.clear_buffers ();

	// Fade out
	_fadeout.force (1);

	// Mixing parameters
	_cr._gain [0].clear_buffers ();
	_cr._gain [0].set_initial_flag (true);
	_cr._gain [1].clear_buffers ();
	_cr._gain [1].set_initial_flag (true);

	_gen_flag = false;
}



void	Voice::cut ()
{
	_fadeout.set (0);
}



void	Voice::deactivate ()
{
	start_sample (0);

	assert (! is_active ());
}



bool	Voice::is_active () const
{
	const bool		spl_flag = spl.is_active ();
	const bool		vol_flag =
		(   _fadeout.get_vol_target () > 0
		 || _fadeout.is_ramping ());

	/*** To do: take filter into account ***/

	return (spl_flag && vol_flag);
}



bool	Voice::has_generated_something () const
{
	return (_gen_flag);
}



bool	Voice::is_compatible_with_group (int group) const
{
	assert (group >= 0);

	return (group == 0 || group == spl.get_group ());
}



// Applies the fadeout
void	Voice::process_post (float spl_ptr [], long nbr_spl, int stereo)
{
	assert (spl_ptr != 0);
	assert (nbr_spl > 0);
	assert (stereo > 0);
	assert (stereo <= 2);

	if (_fadeout.is_ramping ())
	{
		if (stereo == 2)
		{
			GainSmooth		fo_right (_fadeout);
			_fadeout.replace (spl_ptr,     spl_ptr,     nbr_spl, 2, 2);
			fo_right.replace (spl_ptr + 1, spl_ptr + 1, nbr_spl, 2, 2);
		}
		else
		{
			assert (stereo == 1);

			_fadeout.replace (spl_ptr, spl_ptr, nbr_spl, 1, 1);
		}

		if (   _fadeout.get_vol_cur () == 0
			 && spl.has_hq_interpolator ())
		{
			spl.recons_filter_ptr->clear_buffer ();
		}
	}

	_gen_flag = true;
}



// Version without audio handling
void	Voice::process_post ()
{
	if (_fadeout.is_ramping ())
	{
		_fadeout.clear_buffers ();
	}

	_gen_flag = true;
}



Voice::Sample::~Sample ()
{
	delete_hq_interpolator ();
}



void	Voice::Sample::copy_state (const Sample &other)
{
	assert (&other != 0);

	if (&other != this)
	{
		const bool		hq_flag = other.has_hq_interpolator ();
		set_hq_interpolator (hq_flag);
// On ne duplique pas l'etat de l'interpolateur parce qu'en general, on se
// sert de cette fonction pour faire un "retrig" sur le sample. La voie
// dupliquee doit alors partir sur quelque chose de nouveau, mais avec le
// meme parametrage. Si le contenu de l'interpolateur etait duplique, on
// se retrouverait avec ses buffers deverses deux fois dans la musique,
// une fois avec le sample qui se meurt (ok) et l'autre avec le nouveau
// sample (pas bon).
#if 0
		if (hq_flag)
		{
			recons_filter_ptr->copy_state (*other.recons_filter_ptr);
		}
#endif

		start (other._spl_index);

		_rate        = other._rate;
		_curpos_int  = other._curpos_int;
		_curpos_frac = other._curpos_frac;
		_back_flag   = other._back_flag;
	}
}



void	Voice::Sample::start (int spl_index)
{
	assert (spl_index >= 0);
	assert (spl_index <= GTK_NBRSAMPLES_TOTAL);

	_spl_index = spl_index;

	sample_ptr  = SAMP_get_sample_data_adr (spl_index);
	loopbuf_ptr = SAMP_get_sample_loopbuf_adr (spl_index);
	loopbuf_len = SAMP_get_sample_loopbuf (spl_index);
	resol       = SAMP_get_sample_resolution (spl_index) >> 3;
	tracks      = SAMP_get_sample_stereo (spl_index);
	freq        = SAMP_get_sample_freq (spl_index);
	loopmode    = WORD (SAMP_get_sample_loop (spl_index));

	// Loop
	if (loopmode == WaveForm_LOOP_TYPE_NONE)
	{
		reppos = SAMP_get_sample_length (spl_index);
		replen = 0;
	}
	else
	{
		reppos = SAMP_get_sample_repeat (spl_index);
		replen = SAMP_get_sample_replen (spl_index);
	}

	_group = SAMP_get_sample_group (spl_index);

	// Direct-2-Disk
	if (SAMP_get_sample_type (spl_index) == 1)
	{
		d2d_flag             = true;
		d2d.file_ptr         = SAMP_get_sample_file_ptr (spl_index);
		d2d.file_data_offset = SAMP_get_sample_file_data_offset (spl_index);
		d2d.startbuf_ptr     = SAMP_get_sample_startbuf_ptr (spl_index);
		d2d.startbuf_len     = SAMP_get_sample_buffer_len (spl_index, 0);
		d2d.midbuf1_len      = SAMP_get_sample_buffer_len (spl_index, 1);
		d2d.midbuf2_len      = SAMP_get_sample_buffer_len (spl_index, 2);
	}
	else
	{
		d2d_flag = false;
	}

	set_direction (false);
	set_playback_pos (0, 0);
}



void	Voice::Sample::set_hq_interpolator (bool hq_flag)
{
	if (hq_flag)
	{
		if (! has_hq_interpolator ())
		{
			recons_filter_ptr = new ReconstructionFilter (MIX_recons_filter_width);
		}
	}
	else
	{
		delete_hq_interpolator ();
	}
}



bool	Voice::Sample::has_hq_interpolator () const
{
	return (recons_filter_ptr != 0);
}



void	Voice::Sample::set_rate (double rate)
{
	assert (rate > 0);

	_rate = rate;
}



double	Voice::Sample::get_rate () const
{
	return (_rate);
}



void	Voice::Sample::set_playback_pos (long curpos_int, ULWORD curpos_frac)
{
	_curpos_int  = curpos_int;
	_curpos_frac = curpos_frac;
}



long	Voice::Sample::get_playback_pos_int () const
{
	return (_curpos_int);
}



ULWORD	Voice::Sample::get_playback_pos_frac () const
{
	return (_curpos_frac);
}



bool	Voice::Sample::is_active () const
{
	const long		sample_length = compute_len ();

	return (   (   _curpos_int >= 0
	            && _curpos_int < sample_length
	        || (   loopmode == WaveForm_LOOP_TYPE_PP
	            && _curpos_int == -1
	            && _curpos_frac != 0
	            && reppos == 0)));
}



void	Voice::Sample::set_direction (bool back_flag)
{
	_back_flag = back_flag;
}



bool	Voice::Sample::is_running_backward () const
{
	return (_back_flag);
}



int	Voice::Sample::get_group () const
{
	return (_group);
}



void	Voice::Sample::advance_cursor (long nbr_spl, double rate)
{
	assert (nbr_spl > 0);
	assert (rate > 0);

	const long		sample_length = compute_len ();

	// Finished ?
	if (! is_active ())
	{
		return;
	}

	double			curpos_dbl = _curpos_int + _curpos_frac * (1.0/4294967296.0);

	/* Calcule la nouvelle position du sample */
	const double	extent = nbr_spl * rate;
	double			newpos_dbl = curpos_dbl;
	if (_back_flag)
	{
		newpos_dbl -= extent;
	}
	else
	{
		newpos_dbl += extent;
	}

	/* Si on est en ping-pong, marche arriere et qu'on depasse
		de la boucle a gauche, on se remet en marche avant pour
		traiter le bouclage. */
	if (   loopmode == WaveForm_LOOP_TYPE_PP
		 && _back_flag
		 && newpos_dbl <= reppos - 1)
	{
		_back_flag = false;
		newpos_dbl = (reppos * 2 - 1) - newpos_dbl;
	}

	using namespace std;
	double			dummy;
	ULWORD			newpos_frac = static_cast <ULWORD> (
		modf (newpos_dbl, &dummy) * 4294967296.0
	);
	long				newpos_int = static_cast <long> (floor (newpos_dbl));

	/* Si on depasse du sample a droite, donc qu'on est dirige vers l'avant */
	if (newpos_int >= sample_length)
	{
		/* Bouclage normal */
		if (loopmode == WaveForm_LOOP_TYPE_FWD)
		{
			newpos_int -= sample_length;
			newpos_int %= replen;
			newpos_int += reppos;
		}

		/* Ping-pong loop */
		else if (loopmode == WaveForm_LOOP_TYPE_PP)
		{
			newpos_int -= reppos;
			newpos_int %= replen * 2;
			if (newpos_int >= replen)
			{
				_back_flag = true;
				newpos_int = (replen * 2 - 1) - newpos_int;
				if (newpos_frac != 0)
				{
					newpos_int --;
					newpos_frac = ULWORD (-SLWORD (newpos_frac));
				}
			}
			newpos_int += reppos;
		}
	}

	_curpos_int  = newpos_int;
	_curpos_frac = newpos_frac;
}



void	Voice::Sample::process_block (float out_ptr [], long nbr_spl, int d_stereo, int voice_index)
{
	assert (out_ptr != 0);
	assert (nbr_spl > 0);

	const long		sample_length = compute_len ();
	const int		bytes_per_sample = resol * tracks;

	const long		vol = 0x8000L * d_stereo;

	/* Calcul de la frequence relative */
	const int		freq_int = static_cast <int> (floor (_rate));
	const ULWORD	freq_frac = static_cast <ULWORD> ((_rate - freq_int) * 4294967296.0);

	/* Calcul de la position fractionnaire dans le buffer lineaire, qui est
	   toujours lu vers l'avant */
	ULWORD			linear_curpos_frac = _curpos_frac;
	if (_back_flag)
	{
		linear_curpos_frac = ULWORD (-SLWORD (_curpos_frac));
	}

	/* Comme on est oblige de lineariser le sample, on impose une
		contrainte de frequence maximum sur le sample. */
	if (freq_int < MIX_LINEAR_BUFFER_MAX_FREQ_RATIO)
	{
		/* Linearisation du sample */
		linearise_data (
			MIX_linear_sample_buffer,
			voice_index,
			static_cast <long> (ceil (nbr_spl * _rate) + 3)
		);

		/* Reechantillonnage par filtre de reconstruction */
		if (has_hq_interpolator ())
		{
			recons_filter_ptr->resample_and_mix (
				MIX_linear_sample_buffer,
				out_ptr,
				nbr_spl,
				tracks,
				d_stereo,
				vol,
				linear_curpos_frac,
				_rate
			);
		}

		/* Reechantillonnage avec interpolation */
		else if (interpol_flag)
		{
			resample_lerp (
				MIX_linear_sample_buffer,
				out_ptr,
				nbr_spl,
				tracks,
				d_stereo,
				vol,
				linear_curpos_frac,
				freq_int,
				freq_frac
			);
		}

		/* Reechantillonnage sans interpolation */
		else
		{
			resample_snh (
				MIX_linear_sample_buffer,
				out_ptr,
				nbr_spl,
				tracks,
				d_stereo,
				vol,
				linear_curpos_frac,
				freq_int,
				freq_frac
			);
		}
	}

	/* Si la frequence est trop haute, on met du blanc si on est en temps reel */
	else
	{
		bool				silence_flag = true;

		if (has_hq_interpolator ())
		{
			const long		lin_buffer_len =
				static_cast <long> (ceil (nbr_spl * _rate) + 3);
			SWORD *			lin_buffer_ptr =
				(SWORD *) MALLOC (lin_buffer_len * tracks * sizeof (*lin_buffer_ptr));
			if (lin_buffer_ptr != 0)
			{
				linearise_data (lin_buffer_ptr, voice_index, lin_buffer_len);
				recons_filter_ptr->resample_and_mix (
					lin_buffer_ptr,
					out_ptr,
					nbr_spl,
					tracks,
					d_stereo,
					vol,
					linear_curpos_frac,
					_rate
				);
				FREE (lin_buffer_ptr);

				silence_flag = false;
			}
		}

		if (silence_flag)
		{
			memset (out_ptr, 0, nbr_spl * d_stereo * sizeof (*out_ptr));
		}
	}
}



void	Voice::Filter::activate (double f1, double f2, double q1, double q2)
{
	assert (f1 > 0);
	assert (f2 > 0);
	assert (q1 > 0);
	assert (q2 > 0);

	_active_flag = true;
	_freq [0] = f1;
	_freq [1] = f2;
	_q [0] = q1;
	_q [0] = q2;

	_lerp_freq = 0;
	_lerp_q = 0;

	_final_freq = 0;
	_final_q = 0;
}



void	Voice::Filter::deactivate ()
{
	_active_flag = false;
}



bool	Voice::Filter::is_active () const
{
	return (_active_flag);
}



void	Voice::Filter::set_lerp_freq (double lerp_freq)
{
	assert (lerp_freq >= 0);
	assert (lerp_freq <= 1);

	_lerp_freq = lerp_freq;
}



void	Voice::Filter::set_lerp_q (double lerp_q)
{
	assert (lerp_q >= 0);
	assert (lerp_q <= 1);

	_lerp_q = lerp_q;
}



void	Voice::Filter::compute_final_data (double note_freq, const EnvSet &env_set)
{
	assert (note_freq > 0);


	// Calcule la frequence de base
	double			base_freq = _freq [0];
	if (base_freq <= 20.0)
	{
		base_freq *= note_freq;
	}

	// Ajuste si necessaire la frequence sur la velocite
	double			min_vol_freq = _freq [1];
	if (min_vol_freq <= 20.0)
	{
		min_vol_freq *= note_freq;
	}

	base_freq += (min_vol_freq - base_freq) * _lerp_freq;

	// Resonnance de base, ajuste si necessaire la resonnance sur la velocite
	double			base_q = _q [0] + (_q [1] - _q [0]) * _lerp_q;

	// Tient en compte les enveloppes
	if (env_set._com_env [Envelope_TYPE_CUTOFF].nbr > 0)
	{
		base_freq *= exp (
			env_set._com_env [Envelope_TYPE_CUTOFF].proc.final_val * LN2 / 1200
		);
	}
	if (env_set._com_env [Envelope_TYPE_RESO].nbr > 0)
	{
		base_q *= exp (
			env_set._com_env [Envelope_TYPE_RESO].proc.final_val * LN10 / 2000
		);
	}

	// Limitations
	base_freq = MAX (base_freq, 20.0);
	base_q = MIN (base_q, 100.0);
	base_q = MAX (base_q, 0.5);

	// Si on a change les valeurs du filtre depuis la derniere fois, on recalcule les coefficients
	if (   base_freq != _final_freq
	    || base_q != _final_q)
	{
		if (base_freq * 2.2 > MIX_replay_freq)
		{
			coef_x [0] = 1;
			coef_x [1] = 0;
			coef_x [2] = 0;
			coef_y [1] = 0;
			coef_y [2] = 0;
		}

		else
		{
			const double	k = 1.0 / tan (PI * base_freq / MIX_replay_freq);
			const double	c = base_q / (k * (base_q * k + 1) + base_q);
			coef_x [0] = c;
			coef_x [1] = c * 2;
			coef_x [2] = c;
			coef_y [1] = c * (1.0 - k*k) * 2;
			coef_y [2] = c * (k*k - k / base_q + 1);
		}
	}

	_final_freq = base_freq;
	_final_q = base_q;
}



void	Voice::Filter::clear_buffers ()
{
	for (int spl = 0; spl < BUF_LEN * 2; spl ++)
	{
		buffer_x [spl] = 0;
		buffer_y [spl] = 0;
	}
	buffer_pos = 0;
}



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



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



void	Voice::Sample::delete_hq_interpolator ()
{
	if (recons_filter_ptr != 0)
	{
		delete recons_filter_ptr;
		recons_filter_ptr = 0;
	}
}



long	Voice::Sample::compute_len () const
{
	return (reppos + replen);
}



/*==========================================================================*/
/*      Nom: linearise_data                                                 */
/*      Description: Copie une partie d'un sample dans un buffer en         */
/*                   developpant si necessaire la boucle. Le buffer est     */
/*                   toujours 16 bits (conversion du sample si necessaire). */
/*      Parametres en entree:                                               */
/*        - voice_index: Numero de la voix qui joue le sample.              */
/*        - nbr_spl: longueur du developpement, en samples.                 */
/*      Parametres en sortie:                                               */
/*        - buffer_ptr: adresse du buffer ou doit etre developpe le sample. */
/*      Parametres en entree/sortie:                                        */
/*      Retour: 0 si tout s'est bien passe.                                 */
/*==========================================================================*/

int	Voice::Sample::linearise_data (SWORD *buffer_ptr, int voice_index, long nbr_spl) const
{
	const int		sample_mul = tracks * resol;
	bool				cur_back_flag = _back_flag;
	long				cur_pos = _curpos_int;
	if (cur_back_flag && _curpos_frac != 0)
	{
		cur_pos ++;
	}

	while (nbr_spl > 0)
	{
		void *			src_ptr;
		long				new_pos;
		bool				new_back_flag;
		bool				back_flag;
		long				block_len;
		find_linear_block (
			voice_index,
			cur_pos,
			cur_back_flag,
		   new_pos,
			new_back_flag,
			src_ptr,
		   block_len,
			back_flag
		);
		/* Sample fini */
		if (block_len == -1)
		{
			fill_buffer_end (buffer_ptr, tracks, nbr_spl, cur_back_flag);
			break;
		}

		/* Donnees indisponibles */
		else if (block_len == -2)
		{
			memset (buffer_ptr, 0, nbr_spl * tracks * 2);
			break;
		}

		block_len = MIN (block_len, nbr_spl);

		/* Vers l'arriere */
		if (back_flag)
		{
			SPLH_copy_mem_invert_convert_sample_2_16 (
				buffer_ptr,
				src_ptr,
			   block_len,
				tracks,
				resol
			);
		}

		/* Vers l'avant */
		else
		{
			SPLH_copy_mem_convert_sample_2_16 (
				buffer_ptr,
				src_ptr,
			   block_len,
				tracks,
				resol
			);
		}

		cur_pos = new_pos;
		cur_back_flag = new_back_flag;
		nbr_spl -= block_len;
		buffer_ptr += block_len * tracks;
	}

	return (0);
}



/*==========================================================================*/
/*      Nom: find_linear_block                                              */
/*      Description: Recherche le plus grand bloc continu de sample dispo   */
/*                   a partir de la position et de la direction donnee.     */
/*                   Donne l'adresse de ce bloc, sa longueur et les         */
/*                   nouvelles directions et positions apres le bloc.       */
/*      Parametres en entree:                                               */
/*        - voice_index: numero de la voix qui joue le sample.              */
/*        - old_position: ancienne position dans le sample, en samples.     */
/*                        -1 signifie que le sample est termine (si la      */
/*                           direction est vers l'arriere).                 */
/*        - old_back_flag: ancienne direction (false = fwd, true = backwd). */
/*      Parametres en sortie:                                               */
/*        - position: Position dans le sample, en samples.                  */
/*                        -1 signifie que le sample est termine (si la      */
/*                           direction est vers l'arriere).                 */
/*        - new_back_flag: Nouvelle direction (false = fwd, true = backwd)  */
/*        - block_ptr: Adresse du bloc lineaire trouve. Si la direction est */
/*                     vers l'arriere, cette variable pointe sur le dernier */
/*                     sample du block.                                     */
/*        - block_len: Longueur en sample du bloc trouve.                   */
/*                     -1 indique que le sample est fini,                   */
/*                     -2 indique que les donnees ne sont pas disponibles   */
/*                        (pour le D2D).                                    */
/*        - back_flag: indique la direction dans laquelle doit etre lu le   */
/*                     trouve.                                              */
/*==========================================================================*/

void	Voice::Sample::find_linear_block (int voice_index, long old_position, bool old_back_flag, long &position, bool &new_back_flag, void * &block_ptr, long &block_len, bool &back_flag) const
{
	const int		sample_mul    = tracks * resol;
	const long		sample_length = compute_len ();

/*______________________________________________
 *
 * Direct-2-Disk
 *______________________________________________
 */

	if (d2d_flag)
	{
		const D2dBuffer &	buffer_info = d2d._desc;

		/* On est dans le buffer de bouclage */
		if (   (   ! old_back_flag
		        && old_position == reppos
				  && loopmode != WaveForm_LOOP_TYPE_PP)
		    || (   old_back_flag
		        && old_position == sample_length - 1
		        && loopmode == WaveForm_LOOP_TYPE_PP))
		{
			block_ptr = loopbuf_ptr;
			block_len = loopbuf_len;
			back_flag = 0;

			/* Arriere */
			if (old_back_flag)
			{
				position = loopbuf_len % (replen * 2);
				
				/* Nouvelle direction vers l'arriere */
				if (position < replen)
				{
					position = reppos + replen - position - 1;
					new_back_flag = true;
				}
				
				/* Nouvelle direction vers l'avant */
				else
				{
					position += reppos - replen;
					new_back_flag = false;
				}
			}

			/* Avant */
			else
			{
				new_back_flag = false;

				/* Bouclage normal */
				if (loopmode == WaveForm_LOOP_TYPE_FWD)
				{
					position = reppos + (loopbuf_len % replen);
				}

				/* Pas de bouclage */
				else
				{
					position = reppos;
					block_len = -1;
				}
			}
		}

		else
		{
			long				buf_start;
			long				buf_length;

			/* On est dans le buffer de debut */
			if (old_position < d2d.startbuf_len)
			{
				block_ptr = (BYTE *)d2d.startbuf_ptr + old_position * sample_mul;
				buf_start = 0;
				buf_length = d2d.startbuf_len;
			}

			/* On est dans un des deux buffers de D2D */
			else
			{
				int				buffer;

				/* Si les buffers de D2D existent reellement */
				if (buffer_info.buffer_ptr [0] != NULL)
				{
					for (buffer = 0; buffer < 2; buffer ++)
					{
						if (buffer_info.buf_flag [buffer]
							 && old_position >= buffer_info.buf_pos [buffer]
							 && old_position <   buffer_info.buf_pos [buffer]
													 + buffer_info.buf_length [buffer])
						{
							break;
						}
					}
				}

				/* Le buffer de D2D n'est pas encore cree (manque de memoire
				   par exemple) */
				else
				{
					buffer = 2;
				}

				/* On est bien dans un des buffers */
				if (buffer < 2)
				{
					block_ptr =   (BYTE *)buffer_info.buffer_ptr [buffer]
						         +   (old_position - buffer_info.buf_pos [buffer])
						           * sample_mul;
					buf_start =  buffer_info.buf_pos [buffer];
					buf_length = buffer_info.buf_length [buffer];
				}

				/* Les donnees qu'on recherche ne sont pas disponibles en RAM */
				else
				{
					block_len = -2;
					block_ptr = NULL;
					new_back_flag = old_back_flag;
					position = old_position;
					back_flag = 0;
				}
			}

			/* Ajuste le reste des donnees */
			if (block_ptr != NULL)
			{
				new_back_flag = old_back_flag;
				long				new_length;

				/* Arriere */
				if (old_back_flag)
				{
					new_length = old_position - buf_start + 1;
					new_length = MIN (new_length, old_position + 1);
					back_flag = 1;
					position = old_position - new_length;

					/* On sort de la boucle ? */
					if (   loopmode == WaveForm_LOOP_TYPE_PP
					    && old_position  >= reppos
						 && position <= reppos)
					{
						position = reppos;
						new_back_flag = false;
						new_length = old_position - reppos + 1;
					}
				}

				/* Avant */
				else
				{
					new_length = buf_start + buf_length - old_position;
					new_length = MIN (new_length, sample_length - old_position);
					back_flag = 0;
					position = old_position + new_length;

					/* On depasse ? */
					if (position + new_length >= sample_length)
					{
						if (loopmode == WaveForm_LOOP_TYPE_PP)
						{
							new_back_flag = true;
							position = sample_length - 1;
						}

						else
						{
							position = reppos;
						}
					}
				}

				block_len = new_length;
			}
		}
	}

/*______________________________________________
 *
 * Memoire
 *______________________________________________
 */

	else
	{
		/* Vers l'avant */
		if (! old_back_flag)
		{
			back_flag = 0;

			/* Buffer de bouclage */
			if (   old_position == reppos
			    && (   replen < loopbuf_len
			        || loopmode == WaveForm_LOOP_TYPE_NONE)
				 && loopmode != WaveForm_LOOP_TYPE_PP)
			{
				block_ptr = loopbuf_ptr;
				block_len = loopbuf_len;
				new_back_flag = false;

				/* Bouclage normal */
				if (loopmode == WaveForm_LOOP_TYPE_FWD)
				{
					position = reppos + (loopbuf_len % replen);
				}

				/* Pas de bouclage */
				else
				{
					position = reppos;
					block_len = -1;
				}
			}

			/* Sample normal */
			else
			{
				block_len = sample_length - old_position;
				block_ptr = (BYTE *)sample_ptr + old_position * sample_mul;

				/* Ping-pong loop */
				if (loopmode == WaveForm_LOOP_TYPE_PP)
				{
					position = sample_length - 1;
					new_back_flag = true;
				}

				/* Bouclage normal ou pas de bouclage */
				else
				{
					position = reppos;
					new_back_flag = false;
				}
			}
		}

		/* Vers l'arriere */
		else
		{
			/* Buffer de bouclage */
			if (   old_position == sample_length - 1
			    && replen < loopbuf_len
				 && loopmode == WaveForm_LOOP_TYPE_PP)
			{
				back_flag = 0;
				block_ptr = loopbuf_ptr;
				block_len = loopbuf_len;
				position = loopbuf_len % (replen * 2);
				
				/* Nouvelle direction vers l'arriere */
				if (position < replen)
				{
					position = reppos + replen - position - 1;
					new_back_flag = true;
				}
				
				/* Nouvelle direction vers l'avant */
				else
				{
					position += reppos - replen;
					new_back_flag = false;
				}
			}

			/* Sample normal */
			else
			{
				/* Loop et on est dans la boucle (ping-pong) */
				if (   loopmode == WaveForm_LOOP_TYPE_PP
				    && old_position >= reppos)
				{
					block_ptr = (BYTE *)sample_ptr + old_position * sample_mul;
					block_len = old_position - reppos + 1;
					position = reppos;
					new_back_flag = false;
					back_flag = 1;
				}

				/* Sample termine */
				else if (old_position < 0)
				{
					position = -1;
					block_len = -1;
					block_ptr = sample_ptr;
					new_back_flag = true;
					back_flag = 0;
				}

				/* Avant de la boucle ou pas de ping-pong loop du tout */
				else
				{
					block_len = old_position + 1;
					block_ptr = (BYTE *)sample_ptr + old_position * sample_mul;
					position = -1;
					new_back_flag = true;
					back_flag = 1;
				}
			}
		}
	}
}



/*==========================================================================*/
/*      Nom: fill_buffer_end                                                */
/*      Description: Remplit la fin du buffer 16 bits quand un sample est   */
/*                   termine.                                               */
/*      Parametres en entree:                                               */
/*        - d_stereo: nombre de voies du buffer destination (1 ou 2).       */
/*        - nbr_spl: longueur a mixer, en samples.                          */
/*        - voice: informations de la voix du sample.                       */
/*        - back_flag: false si on avance vers l'avant, true vers l'arriere.*/
/*      Parametres en entree/sortie:                                        */
/*        - out_ptr: pointeur sur le buffer 16 bits destination.            */
/*==========================================================================*/

void	Voice::Sample::fill_buffer_end (SWORD *out_ptr, int d_stereo, long nbr_spl, bool back_flag) const
{
	void *			data_ptr;
	if (back_flag)
	{
		data_ptr = sample_ptr;
	}
	else
	{
		data_ptr = loopbuf_ptr;
	}

	SLWORD			left_sample;
	SLWORD			right_sample;

	/* Motif 16 bits */
	if (resol == 2)
	{
		left_sample = *((SWORD *)data_ptr);
		if (tracks == 1)
		{
			right_sample = left_sample;
		}
		else
		{
			right_sample = *((SWORD *)data_ptr + 1);
		}
	}

	/* Motif 8 bits */
	else
	{
		left_sample = *((SBYTE *)data_ptr) << 8;
		if (tracks == 1)
		{
			right_sample = left_sample;
		}
		else
		{
			right_sample = *((SBYTE *)data_ptr + 1) << 8;
		}
	}

	/* Remplissage stereo */
	if (d_stereo == 2)
	{
		for ( ; nbr_spl > 0; nbr_spl --)
		{
			*out_ptr++ = (SWORD)left_sample;
			*out_ptr++ = (SWORD)right_sample;
			left_sample = left_sample * 3 / 4;
			right_sample = right_sample * 3 / 4;
		}
	}

	/* Remplissage mono */
	else
	{
		left_sample = (left_sample + right_sample) / 2;
		for ( ; nbr_spl > 0; nbr_spl --)
		{
			*out_ptr++ = (SWORD)left_sample;
			left_sample = left_sample * 3 / 4;
		}
	}
}



/*==========================================================================*/
/*      Nom: resample_snh                                                   */
/*      Description: Reechantillonne et mixe un buffer 16 bits sur un autre */
/*                   buffer float, avec modification du volume. La stereo   */
/*                   de chaque buffer est prise en compte. Les donnes dest. */
/*                   sont ecrasees. Les overflows ne sont pas controles.    */
/*      Parametres en entree:                                               */
/*        - in_ptr: adresse du buffer source.                               */
/*        - nbr_spl: longueur a mixer, en samples.                          */
/*        - s_stereo: nombre de voies du buffer source (1 ou 2).            */
/*        - d_stereo: nombre de voies du buffer destination (1 ou 2).       */
/*        - volume: volume de mixage (0...10000...?).                       */
/*        - pos_frac: position fractionnaire du reechantillonnage (sur un   */
/*                    mot long).                                            */
/*        - freq_int: frequence relative de reechantillonnage, partie       */
/*                    entiere.                                              */
/*        - freq_frac: frequence relative de reechantillonnage, partie      */
/*                     fractionnaire (sur un mot long).                     */
/*      Parametres en entree/sortie:                                        */
/*        - out_ptr: adresse du buffer destination.                         */
/*==========================================================================*/

void	Voice::Sample::resample_snh (const SWORD *in_ptr, float *out_ptr, long nbr_spl, int s_stereo, int d_stereo, SLWORD volume, ULWORD pos_frac, int freq_int, ULWORD freq_frac)
{
	assert (nbr_spl > 0);

	const float		mult_vol = 1.0f / 256;
	const float		vol = volume  * mult_vol;
	const float		vol_mono = vol * 2;

	switch ((s_stereo << 2) + d_stereo)
	{
	/* Mono -> Mono */
	case	(1<<2) + 1:
		do
		{
			*out_ptr++ = in_ptr [0] * vol_mono;
			MIX_STEP (in_ptr, pos_frac, freq_int, freq_frac, 1);
			nbr_spl --;
		}
		while (nbr_spl > 0);
		break;

	/* Mono -> Stereo */
	case	(1<<2) + 2:
		do
		{
			const float		val = in_ptr [0] * vol;
			*out_ptr++ = val;
			*out_ptr++ = val;
			MIX_STEP (in_ptr, pos_frac, freq_int, freq_frac, 1);
			nbr_spl --;
		}
		while (nbr_spl > 0);
		break;

	/* Stereo -> Mono */
	case	(2<<2) + 1:
		freq_int *= 2;
		do
		{
			*out_ptr++ = (in_ptr [0] + in_ptr [1]) * vol;
			MIX_STEP (in_ptr, pos_frac, freq_int, freq_frac, 2);
			nbr_spl --;
		}
		while (nbr_spl > 0);
		break;

	/* Stereo -> Stereo */
	case	(2<<2) + 2:
		freq_int *= 2;
		do
		{
			*out_ptr++ = in_ptr [0] * vol;
			*out_ptr++ = in_ptr [1] * vol;
			MIX_STEP (in_ptr, pos_frac, freq_int, freq_frac, 2);
			nbr_spl --;
		}
		while (nbr_spl > 0);
		break;
	}
}



/*==========================================================================*/
/*      Nom: resample_lerp                                                  */
/*      Description: Reechantillonne et mixe un buffer 16 bits sur un autre */
/*                   buffer float, avec modification du volume. La stereo   */
/*                   de chaque buffer est prise en compte. Les donnees sont */
/*                   ecrasees. Les overflows ne sont pas controles.         */
/*                   Il y a interpolation lors du reechantillonnage.        */
/*                   Le sample source doit comporter un echantillon de plus */
/*                   que la longueur annoncee a cause de l'interpolation.   */
/*      Parametres en entree:                                               */
/*        - in_ptr: adresse du buffer source.                               */
/*        - nbr_spl: longueur a mixer, en samples.                          */
/*        - s_stereo: nombre de voies du buffer source (1 ou 2).            */
/*        - d_stereo: nombre de voies du buffer destination (1 ou 2).       */
/*        - volume: volume de mixage (0...10000...?).                       */
/*        - pos_frac: position fractionnaire du reechantillonnage (sur un   */
/*                    mot long).                                            */
/*        - freq_int: frequence relative de reechantillonnage, partie       */
/*                    entiere.                                              */
/*        - freq_frac: frequence relative de reechantillonnage, partie      */
/*                     fractionnaire (sur un mot long).                     */
/*      Parametres en entree/sortie:                                        */
/*        - out_ptr: adresse du buffer destination.                         */
/*==========================================================================*/

void	Voice::Sample::resample_lerp (const SWORD *in_ptr, float *out_ptr, long nbr_spl, int s_stereo, int d_stereo, SLWORD volume, ULWORD pos_frac, int freq_int, ULWORD freq_frac)
{
	assert (nbr_spl > 0);

	const float		mult_frac = 1.0f / 4294967296.0f;
	const float		mult_vol = 1.0f / 256;
	const float		vol = volume * mult_vol;
	const float		vol_mono = vol * 2;

	switch ((s_stereo << 2) + d_stereo)
	{
	/* Mono -> Mono */
	case	(1<<2) + 1:
		do
		{
			const float		lerp = pos_frac * mult_frac;
			const float		m_1 = in_ptr [0];
			const float		m_2 = in_ptr [1];
			const float		m = m_1 + lerp * (m_2 - m_1);
			*out_ptr++ = m * vol_mono;
			MIX_STEP (in_ptr, pos_frac, freq_int, freq_frac, 1);
			nbr_spl --;
		}
		while (nbr_spl > 0);
		break;

	/* Mono -> Stereo */
	case	(1<<2) + 2:
		do
		{
			const float		lerp = pos_frac * mult_frac;
			const float		m_1 = in_ptr [0];
			const float		m_2 = in_ptr [1];
			const float		m = m_1 + lerp * (m_2 - m_1);
			const float		out = m * vol;
			*out_ptr++ = out;
			*out_ptr++ = out;
			MIX_STEP (in_ptr, pos_frac, freq_int, freq_frac, 1);
			nbr_spl --;
		}
		while (nbr_spl > 0);
		break;

	/* Stereo -> Mono */
	case	(2<<2) + 1:
		freq_int *= 2;
		do
		{
			const float		lerp = pos_frac * mult_frac;
			const float		m_1 = float (in_ptr [0] + in_ptr [1]);
			const float		m_2 = float (in_ptr [2] + in_ptr [3]);
			const float		m = m_1 + lerp * (m_2 - m_1);
			*out_ptr++ = m * vol;
			MIX_STEP (in_ptr, pos_frac, freq_int, freq_frac, 2);
			nbr_spl --;
		}
		while (nbr_spl > 0);
		break;

	/* Stereo -> Stereo */
	case	(2<<2) + 2:
		freq_int *= 2;
		do
		{
			const float		lerp = pos_frac * mult_frac;
			const float		l_1 = in_ptr [0];
			const float		l_2 = in_ptr [2];
			const float		l = l_1 + lerp * (l_2 - l_1);
			const float		r_1 = in_ptr [1];
			const float		r_2 = in_ptr [3];
			const float		r = r_1 + lerp * (r_2 - r_1);
			*out_ptr++ = l * vol;
			*out_ptr++ = r * vol;
			MIX_STEP (in_ptr, pos_frac, freq_int, freq_frac, 2);
			nbr_spl --;
		}
		while (nbr_spl > 0);
		break;
	}
}



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