/*
   ======================================================================
   envelope.c

   Envelope functions for an LWO2 reader.

   Ernie Wright  16 Nov 00
   ====================================================================== */

#include "../picointernal.h"
#include "lwo2.h"

/*
   ======================================================================
   lwFreeEnvelope()

   Free the memory used by an lwEnvelope.
   ====================================================================== */

void lwFreeEnvelope( lwEnvelope *env ){
	if ( env ) {
		if ( env->name ) {
			_pico_free( env->name );
		}
		lwListFree( env->key, _pico_free );
		lwListFree( env->cfilter, lwFreePlugin );
		_pico_free( env );
	}
}


static int compare_keys( lwKey *k1, lwKey *k2 ){
	return k1->time > k2->time ? 1 : k1->time < k2->time ? -1 : 0;
}


/*
   ======================================================================
   lwGetEnvelope()

   Read an ENVL chunk from an LWO2 file.
   ====================================================================== */

lwEnvelope *lwGetEnvelope( picoMemStream_t *fp, int cksize ){
	lwEnvelope *env;
	lwKey *key;
	lwPlugin *plug;
	unsigned int id;
	unsigned short sz;
	float f[ 4 ];
	int i, nparams, pos, rlen;


	/* allocate the Envelope structure */

	env = _pico_calloc( 1, sizeof( lwEnvelope ) );
	if ( !env ) {
		goto Fail;
	}

	/* remember where we started */

	set_flen( 0 );
	pos = _pico_memstream_tell( fp );

	/* index */

	env->index = getVX( fp );

	/* first subchunk header */

	id = getU4( fp );
	sz = getU2( fp );
	if ( 0 > get_flen() ) {
		goto Fail;
	}

	/* process subchunks as they're encountered */

	while ( 1 ) {
		sz += sz & 1;
		set_flen( 0 );

		switch ( id ) {
		case ID_TYPE:
			env->type = getU2( fp );
			break;

		case ID_NAME:
			env->name = getS0( fp );
			break;

		case ID_PRE:
			env->behavior[ 0 ] = getU2( fp );
			break;

		case ID_POST:
			env->behavior[ 1 ] = getU2( fp );
			break;

		case ID_KEY:
			key = _pico_calloc( 1, sizeof( lwKey ) );
			if ( !key ) {
				goto Fail;
			}
			key->time = getF4( fp );
			key->value = getF4( fp );
			lwListInsert( &env->key, key, compare_keys );
			env->nkeys++;
			break;

		case ID_SPAN:
			if ( !key ) {
				goto Fail;
			}
			key->shape = getU4( fp );

			nparams = ( sz - 4 ) / 4;
			if ( nparams > 4 ) {
				nparams = 4;
			}
			for ( i = 0; i < nparams; i++ )
				f[ i ] = getF4( fp );

			switch ( key->shape ) {
			case ID_TCB:
				key->tension = f[ 0 ];
				key->continuity = f[ 1 ];
				key->bias = f[ 2 ];
				break;

			case ID_BEZI:
			case ID_HERM:
			case ID_BEZ2:
				for ( i = 0; i < nparams; i++ )
					key->param[ i ] = f[ i ];
				break;
			}
			break;

		case ID_CHAN:
			plug = _pico_calloc( 1, sizeof( lwPlugin ) );
			if ( !plug ) {
				goto Fail;
			}

			plug->name = getS0( fp );
			plug->flags = getU2( fp );
			plug->data = getbytes( fp, sz - get_flen() );

			lwListAdd( &env->cfilter, plug );
			env->ncfilters++;
			break;

		default:
			break;
		}

		/* error while reading current subchunk? */

		rlen = get_flen();
		if ( rlen < 0 || rlen > sz ) {
			goto Fail;
		}

		/* skip unread parts of the current subchunk */

		if ( rlen < sz ) {
			_pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR );
		}

		/* end of the ENVL chunk? */

		rlen = _pico_memstream_tell( fp ) - pos;
		if ( cksize < rlen ) {
			goto Fail;
		}
		if ( cksize == rlen ) {
			break;
		}

		/* get the next subchunk header */

		set_flen( 0 );
		id = getU4( fp );
		sz = getU2( fp );
		if ( 6 != get_flen() ) {
			goto Fail;
		}
	}

	return env;

Fail:
	lwFreeEnvelope( env );
	return NULL;
}


/*
   ======================================================================
   lwFindEnvelope()

   Returns an lwEnvelope pointer, given an envelope index.
   ====================================================================== */

lwEnvelope *lwFindEnvelope( lwEnvelope *list, int index ){
	lwEnvelope *env;

	env = list;
	while ( env ) {
		if ( env->index == index ) {
			break;
		}
		env = env->next;
	}
	return env;
}


/*
   ======================================================================
   range()

   Given the value v of a periodic function, returns the equivalent value
   v2 in the principal interval [lo, hi].  If i isn't NULL, it receives
   the number of wavelengths between v and v2.

   v2 = v - i * (hi - lo)

   For example, range( 3 pi, 0, 2 pi, i ) returns pi, with i = 1.
   ====================================================================== */

static float range( float v, float lo, float hi, int *i ){
	float v2, r = hi - lo;

	if ( r == 0.0 ) {
		if ( i ) {
			*i = 0;
		}
		return lo;
	}

	v2 = lo + v - r * ( float ) floor( ( double ) v / r );
	if ( i ) {
		*i = -( int )( ( v2 - v ) / r + ( v2 > v ? 0.5 : -0.5 ) );
	}

	return v2;
}


/*
   ======================================================================
   hermite()

   Calculate the Hermite coefficients.
   ====================================================================== */

static void hermite( float t, float *h1, float *h2, float *h3, float *h4 ){
	float t2, t3;

	t2 = t * t;
	t3 = t * t2;

	*h2 = 3.0f * t2 - t3 - t3;
	*h1 = 1.0f - *h2;
	*h4 = t3 - t2;
	*h3 = *h4 - t2 + t;
}


/*
   ======================================================================
   bezier()

   Interpolate the value of a 1D Bezier curve.
   ====================================================================== */

static float bezier( float x0, float x1, float x2, float x3, float t ){
	float a, b, c, t2, t3;

	t2 = t * t;
	t3 = t2 * t;

	c = 3.0f * ( x1 - x0 );
	b = 3.0f * ( x2 - x1 ) - c;
	a = x3 - x0 - c - b;

	return a * t3 + b * t2 + c * t + x0;
}


/*
   ======================================================================
   bez2_time()

   Find the t for which bezier() returns the input time.  The handle
   endpoints of a BEZ2 curve represent the control points, and these have
   (time, value) coordinates, so time is used as both a coordinate and a
   parameter for this curve type.
   ====================================================================== */

static float bez2_time( float x0, float x1, float x2, float x3, float time,
						float *t0, float *t1 ){
	float v, t;

	t = *t0 + ( *t1 - *t0 ) * 0.5f;
	v = bezier( x0, x1, x2, x3, t );
	if ( fabs( time - v ) > .0001f ) {
		if ( v > time ) {
			*t1 = t;
		}
		else{
			*t0 = t;
		}
		return bez2_time( x0, x1, x2, x3, time, t0, t1 );
	}
	else{
		return t;
	}
}


/*
   ======================================================================
   bez2()

   Interpolate the value of a BEZ2 curve.
   ====================================================================== */

static float bez2( lwKey *key0, lwKey *key1, float time ){
	float x, y, t, t0 = 0.0f, t1 = 1.0f;

	if ( key0->shape == ID_BEZ2 ) {
		x = key0->time + key0->param[ 2 ];
	}
	else{
		x = key0->time + ( key1->time - key0->time ) / 3.0f;
	}

	t = bez2_time( key0->time, x, key1->time + key1->param[ 0 ], key1->time,
				   time, &t0, &t1 );

	if ( key0->shape == ID_BEZ2 ) {
		y = key0->value + key0->param[ 3 ];
	}
	else{
		y = key0->value + key0->param[ 1 ] / 3.0f;
	}

	return bezier( key0->value, y, key1->param[ 1 ] + key1->value, key1->value, t );
}


/*
   ======================================================================
   outgoing()

   Return the outgoing tangent to the curve at key0.  The value returned
   for the BEZ2 case is used when extrapolating a linear pre behavior and
   when interpolating a non-BEZ2 span.
   ====================================================================== */

static float outgoing( lwKey *key0, lwKey *key1 ){
	float a, b, d, t, out;

	switch ( key0->shape )
	{
	case ID_TCB:
		a = ( 1.0f - key0->tension )
			* ( 1.0f + key0->continuity )
			* ( 1.0f + key0->bias );
		b = ( 1.0f - key0->tension )
			* ( 1.0f - key0->continuity )
			* ( 1.0f - key0->bias );
		d = key1->value - key0->value;

		if ( key0->prev ) {
			t = ( key1->time - key0->time ) / ( key1->time - key0->prev->time );
			out = t * ( a * ( key0->value - key0->prev->value ) + b * d );
		}
		else{
			out = b * d;
		}
		break;

	case ID_LINE:
		d = key1->value - key0->value;
		if ( key0->prev ) {
			t = ( key1->time - key0->time ) / ( key1->time - key0->prev->time );
			out = t * ( key0->value - key0->prev->value + d );
		}
		else{
			out = d;
		}
		break;

	case ID_BEZI:
	case ID_HERM:
		out = key0->param[ 1 ];
		if ( key0->prev ) {
			out *= ( key1->time - key0->time ) / ( key1->time - key0->prev->time );
		}
		break;

	case ID_BEZ2:
		out = key0->param[ 3 ] * ( key1->time - key0->time );
		if ( fabs( key0->param[ 2 ] ) > 1e-5f ) {
			out /= key0->param[ 2 ];
		}
		else{
			out *= 1e5f;
		}
		break;

	case ID_STEP:
	default:
		out = 0.0f;
		break;
	}

	return out;
}


/*
   ======================================================================
   incoming()

   Return the incoming tangent to the curve at key1.  The value returned
   for the BEZ2 case is used when extrapolating a linear post behavior.
   ====================================================================== */

static float incoming( lwKey *key0, lwKey *key1 ){
	float a, b, d, t, in;

	switch ( key1->shape )
	{
	case ID_LINE:
		d = key1->value - key0->value;
		if ( key1->next ) {
			t = ( key1->time - key0->time ) / ( key1->next->time - key0->time );
			in = t * ( key1->next->value - key1->value + d );
		}
		else{
			in = d;
		}
		break;

	case ID_TCB:
		a = ( 1.0f - key1->tension )
			* ( 1.0f - key1->continuity )
			* ( 1.0f + key1->bias );
		b = ( 1.0f - key1->tension )
			* ( 1.0f + key1->continuity )
			* ( 1.0f - key1->bias );
		d = key1->value - key0->value;

		if ( key1->next ) {
			t = ( key1->time - key0->time ) / ( key1->next->time - key0->time );
			in = t * ( b * ( key1->next->value - key1->value ) + a * d );
		}
		else{
			in = a * d;
		}
		break;

	case ID_BEZI:
	case ID_HERM:
		in = key1->param[ 0 ];
		if ( key1->next ) {
			in *= ( key1->time - key0->time ) / ( key1->next->time - key0->time );
		}
		break;
		return in;

	case ID_BEZ2:
		in = key1->param[ 1 ] * ( key1->time - key0->time );
		if ( fabs( key1->param[ 0 ] ) > 1e-5f ) {
			in /= key1->param[ 0 ];
		}
		else{
			in *= 1e5f;
		}
		break;

	case ID_STEP:
	default:
		in = 0.0f;
		break;
	}

	return in;
}


/*
   ======================================================================
   evalEnvelope()

   Given a list of keys and a time, returns the interpolated value of the
   envelope at that time.
   ====================================================================== */

float evalEnvelope( lwEnvelope *env, float time ){
	lwKey *key0, *key1, *skey, *ekey;
	float t, h1, h2, h3, h4, in, out, offset = 0.0f;
	int noff;


	/* if there's no key, the value is 0 */

	if ( env->nkeys == 0 ) {
		return 0.0f;
	}

	/* if there's only one key, the value is constant */

	if ( env->nkeys == 1 ) {
		return env->key->value;
	}

	/* find the first and last keys */

	skey = ekey = env->key;
	while ( ekey->next ) ekey = ekey->next;

	/* use pre-behavior if time is before first key time */

	if ( time < skey->time ) {
		switch ( env->behavior[ 0 ] )
		{
		case BEH_RESET:
			return 0.0f;

		case BEH_CONSTANT:
			return skey->value;

		case BEH_REPEAT:
			time = range( time, skey->time, ekey->time, NULL );
			break;

		case BEH_OSCILLATE:
			time = range( time, skey->time, ekey->time, &noff );
			if ( noff % 2 ) {
				time = ekey->time - skey->time - time;
			}
			break;

		case BEH_OFFSET:
			time = range( time, skey->time, ekey->time, &noff );
			offset = noff * ( ekey->value - skey->value );
			break;

		case BEH_LINEAR:
			out = outgoing( skey, skey->next )
				  / ( skey->next->time - skey->time );
			return out * ( time - skey->time ) + skey->value;
		}
	}

	/* use post-behavior if time is after last key time */

	else if ( time > ekey->time ) {
		switch ( env->behavior[ 1 ] )
		{
		case BEH_RESET:
			return 0.0f;

		case BEH_CONSTANT:
			return ekey->value;

		case BEH_REPEAT:
			time = range( time, skey->time, ekey->time, NULL );
			break;

		case BEH_OSCILLATE:
			time = range( time, skey->time, ekey->time, &noff );
			if ( noff % 2 ) {
				time = ekey->time - skey->time - time;
			}
			break;

		case BEH_OFFSET:
			time = range( time, skey->time, ekey->time, &noff );
			offset = noff * ( ekey->value - skey->value );
			break;

		case BEH_LINEAR:
			in = incoming( ekey->prev, ekey )
				 / ( ekey->time - ekey->prev->time );
			return in * ( time - ekey->time ) + ekey->value;
		}
	}

	/* get the endpoints of the interval being evaluated */

	key0 = env->key;
	while ( time > key0->next->time )
		key0 = key0->next;
	key1 = key0->next;

	/* check for singularities first */

	if ( time == key0->time ) {
		return key0->value + offset;
	}
	else if ( time == key1->time ) {
		return key1->value + offset;
	}

	/* get interval length, time in [0, 1] */

	t = ( time - key0->time ) / ( key1->time - key0->time );

	/* interpolate */

	switch ( key1->shape )
	{
	case ID_TCB:
	case ID_BEZI:
	case ID_HERM:
		out = outgoing( key0, key1 );
		in = incoming( key0, key1 );
		hermite( t, &h1, &h2, &h3, &h4 );
		return h1 * key0->value + h2 * key1->value + h3 * out + h4 * in + offset;

	case ID_BEZ2:
		return bez2( key0, key1, time ) + offset;

	case ID_LINE:
		return key0->value + t * ( key1->value - key0->value ) + offset;

	case ID_STEP:
		return key0->value + offset;

	default:
		return offset;
	}
}