/*

    TiMidity -- Experimental MIDI to WAVE converter
    Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>

    Suddenly, you realize that this program is free software; you get
    an overwhelming urge to 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 another copy of the GNU General Public
    License along with this program; if not, write to the Free
    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
    I bet they'll be amazed.

    mix.c */

#include "pent_include.h"

#ifdef USE_TIMIDITY_MIDI

#include <cmath>
#include <cstdio>
#include <cstdlib>

#include "timidity.h"
#include "timidity_common.h"
#include "timidity_instrum.h"
#include "timidity_playmidi.h"
#include "timidity_output.h"
#include "timidity_controls.h"
#include "timidity_tables.h"
#include "timidity_resample.h"
#include "timidity_mix.h"

#ifdef NS_TIMIDITY
namespace NS_TIMIDITY {
#endif

/* Returns 1 if envelope runs out */
int recompute_envelope(int v)
{
	int stage;

	stage = voice[v].envelope_stage;

	if (stage>5)
	{
		/* Envelope ran out. */
		int tmp=(voice[v].status == VOICE_DIE); /* Already displayed as dead */
		voice[v].status = VOICE_FREE;
		if(!tmp)
			ctl->note(v);
		return 1;
	}

	if (voice[v].sample->modes & MODES_ENVELOPE)
	{
		if (voice[v].status==VOICE_ON || voice[v].status==VOICE_SUSTAINED)
		{
			if (stage>2)
			{
				/* Freeze envelope until note turns off. Trumpets want this. */
				voice[v].envelope_increment=0;
				return 0;
			}
		}
	}
	voice[v].envelope_stage=stage+1;

	if (voice[v].envelope_volume==voice[v].sample->envelope_offset[stage])
		return recompute_envelope(v);
	voice[v].envelope_target=voice[v].sample->envelope_offset[stage];
	voice[v].envelope_increment = voice[v].sample->envelope_rate[stage];
	if (voice[v].envelope_target<voice[v].envelope_volume)
		voice[v].envelope_increment = -voice[v].envelope_increment;
	return 0;
}

void apply_envelope_to_amp(int v)
{
	float lamp=voice[v].left_amp;
	if (voice[v].panned == PANNED_MYSTERY)
	{
		float ramp=voice[v].right_amp;
		if (voice[v].tremolo_phase_increment)
		{
			lamp *= voice[v].tremolo_volume;
			ramp *= voice[v].tremolo_volume;
		}
		if (voice[v].sample->modes & MODES_ENVELOPE)
		{
			lamp *= static_cast<float>(vol_table[voice[v].envelope_volume>>23]);
			ramp *= static_cast<float>(vol_table[voice[v].envelope_volume>>23]);
		}

		auto la = static_cast<sint32>(FSCALE(lamp,AMP_BITS));

		if (la>MAX_AMP_VALUE)
			la=MAX_AMP_VALUE;

		auto ra = static_cast<sint32>(FSCALE(ramp,AMP_BITS));
		if (ra>MAX_AMP_VALUE)
			ra=MAX_AMP_VALUE;


		voice[v].left_mix=FINAL_VOLUME(la);
		voice[v].right_mix=FINAL_VOLUME(ra);
	}
	else
	{
		if (voice[v].tremolo_phase_increment)
			lamp *= voice[v].tremolo_volume;
		if (voice[v].sample->modes & MODES_ENVELOPE)
			lamp *= static_cast<float>(vol_table[voice[v].envelope_volume>>23]);

		auto la = static_cast<sint32>(FSCALE(lamp,AMP_BITS));

		if (la>MAX_AMP_VALUE)
			la=MAX_AMP_VALUE;

		voice[v].left_mix=FINAL_VOLUME(la);
	}
}

static int update_envelope(int v)
{
	voice[v].envelope_volume += voice[v].envelope_increment;
	/* Why is there no ^^ operator?? */
	if (((voice[v].envelope_increment < 0) &&
	     (voice[v].envelope_volume <= voice[v].envelope_target)) ||
	    ((voice[v].envelope_increment > 0) &&
	     (voice[v].envelope_volume >= voice[v].envelope_target)))
	{
		voice[v].envelope_volume = voice[v].envelope_target;
		if (recompute_envelope(v))
			return 1;
	}
	return 0;
}

static void update_tremolo(int v)
{
	sint32 depth=voice[v].sample->tremolo_depth<<7;

	if (voice[v].tremolo_sweep)
	{
		/* Update sweep position */

		voice[v].tremolo_sweep_position += voice[v].tremolo_sweep;
		if (voice[v].tremolo_sweep_position>=(1<<SWEEP_SHIFT))
			voice[v].tremolo_sweep=0; /* Swept to max amplitude */
		else
		{
			/* Need to adjust depth */
			depth *= voice[v].tremolo_sweep_position;
			depth >>= SWEEP_SHIFT;
		}
	}

	voice[v].tremolo_phase += voice[v].tremolo_phase_increment;

	/* if (voice[v].tremolo_phase >= (SINE_CYCLE_LENGTH<<RATE_SHIFT))
	voice[v].tremolo_phase -= SINE_CYCLE_LENGTH<<RATE_SHIFT;  */

	voice[v].tremolo_volume = static_cast<float> 
		(1.0 - FSCALENEG((sine(voice[v].tremolo_phase >> RATE_SHIFT) + 1.0)
		                 * depth * TREMOLO_AMPLITUDE_TUNING,
		                 17));

	/* I'm not sure about the +1.0 there -- it makes tremoloed voices'
	 volumes on average the lower the higher the tremolo amplitude. */
}

/* Returns 1 if the note died */
static int update_signal(int v)
{
	if (voice[v].envelope_increment && update_envelope(v))
		return 1;

	if (voice[v].tremolo_phase_increment)
		update_tremolo(v);

	apply_envelope_to_amp(v);
	return 0;
}

#ifdef LOOKUP_HACK
#  define MIXATION(a)	*lp++ += mixup[(a<<8) | (uint8)s];
#else
#  define MIXATION(a)	*lp++ += (a)*s;
#endif

static void mix_mystery_signal(sample_t *sp, sint32 *lp, int v, int count)
{
	Voice *vp = voice + v;
	final_volume_t left=vp->left_mix;
	final_volume_t right=vp->right_mix;
	int cc;
	sample_t s;

	if (!(cc = vp->control_counter))
	{
		cc = control_ratio;
		if (update_signal(v))
			return;	/* Envelope ran out */
		left = vp->left_mix;
		right = vp->right_mix;
	}

	while (count)
		if (cc < count)
		{
			count -= cc;
			while (cc--)
			{
				s = *sp++;
				MIXATION(left);
				MIXATION(right);
			}
			cc = control_ratio;
			if (update_signal(v))
				return;	/* Envelope ran out */
			left = vp->left_mix;
			right = vp->right_mix;
		}
		else
		{
			vp->control_counter = cc - count;
			while (count--)
			{
				s = *sp++;
				MIXATION(left);
				MIXATION(right);
			}
			return;
		}
}

static void mix_center_signal(sample_t *sp, sint32 *lp, int v, int count)
{
	Voice *vp = voice + v;
	final_volume_t 
		left=vp->left_mix;
	int cc;
	sample_t s;

	if (!(cc = vp->control_counter))
	{
		cc = control_ratio;
		if (update_signal(v))
			return;	/* Envelope ran out */
		left = vp->left_mix;
	}

	while (count)
		if (cc < count)
		{
			count -= cc;
			while (cc--)
			{
				s = *sp++;
				MIXATION(left);
				MIXATION(left);
			}
			cc = control_ratio;
			if (update_signal(v))
				return;	/* Envelope ran out */
			left = vp->left_mix;
		}
		else
		{
			vp->control_counter = cc - count;
			while (count--)
			{
				s = *sp++;
				MIXATION(left);
				MIXATION(left);
			}
			return;
		}
}

static void mix_single_signal(sample_t *sp, sint32 *lp, int v, int count)
{
	Voice *vp = voice + v;
	final_volume_t left=vp->left_mix;
	int cc;

	if (!(cc = vp->control_counter))
	{
		cc = control_ratio;
		if (update_signal(v))
			return;	/* Envelope ran out */
		left = vp->left_mix;
	}

	while (count)
		if (cc < count)
		{
			count -= cc;
			while (cc--)
			{
				sample_t s = *sp++;
				MIXATION(left);
				lp++;
			}
			cc = control_ratio;
			if (update_signal(v))
				return;	/* Envelope ran out */
			left = vp->left_mix;
		}
		else
		{
			vp->control_counter = cc - count;
			while (count--)
			{
				sample_t s = *sp++;
				MIXATION(left);
				lp++;
			}
			return;
		}
}

static void mix_mono_signal(sample_t *sp, sint32 *lp, int v, int count)
{
	Voice *vp = voice + v;
	final_volume_t left=vp->left_mix;
	int cc;

	if (!(cc = vp->control_counter))
	{
		cc = control_ratio;
		if (update_signal(v))
			return;	/* Envelope ran out */
		left = vp->left_mix;
	}

	while (count)
		if (cc < count)
		{
			count -= cc;
			while (cc--)
			{
				sample_t s = *sp++;
				MIXATION(left);
			}
			cc = control_ratio;
			if (update_signal(v))
				return;	/* Envelope ran out */
			left = vp->left_mix;
		}
		else
		{
			vp->control_counter = cc - count;
			while (count--)
			{
				sample_t s = *sp++;
				MIXATION(left);
			}
			return;
		}
}

static void mix_mystery(sample_t *sp, sint32 *lp, int v, int count)
{
	final_volume_t left=voice[v].left_mix;
	final_volume_t right=voice[v].right_mix;
	sample_t s;

	while (count--)
	{
		s = *sp++;
		MIXATION(left);
		MIXATION(right);
	}
}

static void mix_center(sample_t *sp, sint32 *lp, int v, int count)
{
	final_volume_t left=voice[v].left_mix;

	while (count--)
	{
		sample_t s = *sp++;
		MIXATION(left);
		MIXATION(left);
	}
}

static void mix_single(sample_t *sp, sint32 *lp, int v, int count)
{
	final_volume_t left=voice[v].left_mix;
	sample_t s;

	while (count--)
	{
		s = *sp++;
		MIXATION(left);
		lp++;
	}
}

static void mix_mono(sample_t *sp, sint32 *lp, int v, int count)
{
	final_volume_t left=voice[v].left_mix;

	while (count--)
	{
		sample_t s = *sp++;
		MIXATION(left);
	}
}

/* Ramp a note out in c samples */
static void ramp_out(sample_t *sp, sint32 *lp, int v, sint32 c)
{
	/* should be final_volume_t, but uint8 gives trouble. */
	sample_t s=0; /* silly warning about uninitialized s */

	/* Fix by James Caldwell */
	if ( c == 0 ) c = 1;

	sint32 left=voice[v].left_mix;
	sint32 li=-(left/c);
	if (!li) li=-1;

	/* printf("Ramping out: left=%d, c=%d, li=%d\n", left, c, li); */

	if (!(play_mode->encoding & PE_MONO))
	{
		if (voice[v].panned==PANNED_MYSTERY)
		{
			sint32 right=voice[v].right_mix;
			sint32 ri=-(right/c);
			while (c--)
			{
				left += li;
				if (left<0)
					left=0;
				right += ri;
				if (right<0)
					right=0;
				s=*sp++;
				MIXATION(left);
				MIXATION(right);
			}
		}
		else if (voice[v].panned==PANNED_CENTER)
		{
			while (c--)
			{
				left += li;
				if (left<0)
					return;
				s=*sp++;
				MIXATION(left);
				MIXATION(left);
			}
		}
		else if (voice[v].panned==PANNED_LEFT)
		{
			while (c--)
			{
				left += li;
				if (left<0)
					return;
				s=*sp++;
				MIXATION(left);
				lp++;
			}
		}
		else if (voice[v].panned==PANNED_RIGHT)
		{
			while (c--)
			{
				left += li;
				if (left<0)
					return;
				s=*sp++;
				lp++;
				MIXATION(left);
			}
		}
	}
	else
	{
		/* Mono output.  */
		while (c--)
		{
			left += li;
			if (left<0)
				return;
			s=*sp++;
			MIXATION(left);
		}
	}
}


/**************** interface function ******************/

void mix_voice(sint32 *buf, int v, sint32 c)
{
	Voice *vp=voice+v;
	sample_t *sp;
	if (vp->status==VOICE_DIE)
	{
		if (c>=MAX_DIE_TIME)
			c=MAX_DIE_TIME;
		sp=resample_voice(v, &c);
		ramp_out(sp, buf, v, c);
		vp->status=VOICE_FREE;
	}
	else
	{
		sp=resample_voice(v, &c);
		if (play_mode->encoding & PE_MONO)
		{
			/* Mono output. */
			if (vp->envelope_increment || vp->tremolo_phase_increment)
				mix_mono_signal(sp, buf, v, c);
			else
				mix_mono(sp, buf, v, c);
		}
		else
		{
			if (vp->panned == PANNED_MYSTERY)
			{
				if (vp->envelope_increment || vp->tremolo_phase_increment)
					mix_mystery_signal(sp, buf, v, c);
				else
					mix_mystery(sp, buf, v, c);
			}
			else if (vp->panned == PANNED_CENTER)
			{
				if (vp->envelope_increment || vp->tremolo_phase_increment)
					mix_center_signal(sp, buf, v, c);
				else
					mix_center(sp, buf, v, c);
			}
			else
			{ 
				/* It's either full left or full right. In either case,
				 every other sample is 0. Just get the offset right: */
				if (vp->panned == PANNED_RIGHT) buf++;

				if (vp->envelope_increment || vp->tremolo_phase_increment)
					mix_single_signal(sp, buf, v, c);
				else 
					mix_single(sp, buf, v, c);
			}
		}
	}
}

#ifdef NS_TIMIDITY
}
#endif

#endif //USE_TIMIDITY_MIDI
