-/*\r
-======================================================================\r
-envelope.c\r
-\r
-Envelope functions for an LWO2 reader.\r
-\r
-Ernie Wright 16 Nov 00\r
-====================================================================== */\r
-\r
-#include "../picointernal.h"\r
-#include "lwo2.h"\r
-\r
-/*\r
-======================================================================\r
-lwFreeEnvelope()\r
-\r
-Free the memory used by an lwEnvelope.\r
-====================================================================== */\r
-\r
-void lwFreeEnvelope( lwEnvelope *env )\r
-{\r
- if ( env ) {\r
- if ( env->name ) _pico_free( env->name );\r
- lwListFree( env->key, _pico_free );\r
- lwListFree( env->cfilter, lwFreePlugin );\r
- _pico_free( env );\r
- }\r
-}\r
-\r
-\r
-static int compare_keys( lwKey *k1, lwKey *k2 )\r
-{\r
- return k1->time > k2->time ? 1 : k1->time < k2->time ? -1 : 0;\r
-}\r
-\r
-\r
-/*\r
-======================================================================\r
-lwGetEnvelope()\r
-\r
-Read an ENVL chunk from an LWO2 file.\r
-====================================================================== */\r
-\r
-lwEnvelope *lwGetEnvelope( picoMemStream_t *fp, int cksize )\r
-{\r
- lwEnvelope *env;\r
- lwKey *key;\r
- lwPlugin *plug;\r
- unsigned int id;\r
- unsigned short sz;\r
- float f[ 4 ];\r
- int i, nparams, pos, rlen;\r
-\r
-\r
- /* allocate the Envelope structure */\r
-\r
- env = _pico_calloc( 1, sizeof( lwEnvelope ));\r
- if ( !env ) goto Fail;\r
-\r
- /* remember where we started */\r
-\r
- set_flen( 0 );\r
- pos = _pico_memstream_tell( fp );\r
-\r
- /* index */\r
-\r
- env->index = getVX( fp );\r
-\r
- /* first subchunk header */\r
-\r
- id = getU4( fp );\r
- sz = getU2( fp );\r
- if ( 0 > get_flen() ) goto Fail;\r
-\r
- /* process subchunks as they're encountered */\r
-\r
- while ( 1 ) {\r
- sz += sz & 1;\r
- set_flen( 0 );\r
-\r
- switch ( id ) {\r
- case ID_TYPE:\r
- env->type = getU2( fp );\r
- break;\r
-\r
- case ID_NAME:\r
- env->name = getS0( fp );\r
- break;\r
-\r
- case ID_PRE:\r
- env->behavior[ 0 ] = getU2( fp );\r
- break;\r
-\r
- case ID_POST:\r
- env->behavior[ 1 ] = getU2( fp );\r
- break;\r
-\r
- case ID_KEY:\r
- key = _pico_calloc( 1, sizeof( lwKey ));\r
- if ( !key ) goto Fail;\r
- key->time = getF4( fp );\r
- key->value = getF4( fp );\r
- lwListInsert( &env->key, key, compare_keys );\r
- env->nkeys++;\r
- break;\r
-\r
- case ID_SPAN:\r
- if ( !key ) goto Fail;\r
- key->shape = getU4( fp );\r
-\r
- nparams = ( sz - 4 ) / 4;\r
- if ( nparams > 4 ) nparams = 4;\r
- for ( i = 0; i < nparams; i++ )\r
- f[ i ] = getF4( fp );\r
-\r
- switch ( key->shape ) {\r
- case ID_TCB:\r
- key->tension = f[ 0 ];\r
- key->continuity = f[ 1 ];\r
- key->bias = f[ 2 ];\r
- break;\r
-\r
- case ID_BEZI:\r
- case ID_HERM:\r
- case ID_BEZ2:\r
- for ( i = 0; i < nparams; i++ )\r
- key->param[ i ] = f[ i ];\r
- break;\r
- }\r
- break;\r
-\r
- case ID_CHAN:\r
- plug = _pico_calloc( 1, sizeof( lwPlugin ));\r
- if ( !plug ) goto Fail;\r
-\r
- plug->name = getS0( fp );\r
- plug->flags = getU2( fp );\r
- plug->data = getbytes( fp, sz - get_flen() );\r
-\r
- lwListAdd( &env->cfilter, plug );\r
- env->ncfilters++;\r
- break;\r
-\r
- default:\r
- break;\r
- }\r
-\r
- /* error while reading current subchunk? */\r
-\r
- rlen = get_flen();\r
- if ( rlen < 0 || rlen > sz ) goto Fail;\r
-\r
- /* skip unread parts of the current subchunk */\r
-\r
- if ( rlen < sz )\r
- _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR );\r
-\r
- /* end of the ENVL chunk? */\r
-\r
- rlen = _pico_memstream_tell( fp ) - pos;\r
- if ( cksize < rlen ) goto Fail;\r
- if ( cksize == rlen ) break;\r
-\r
- /* get the next subchunk header */\r
-\r
- set_flen( 0 );\r
- id = getU4( fp );\r
- sz = getU2( fp );\r
- if ( 6 != get_flen() ) goto Fail;\r
- }\r
-\r
- return env;\r
-\r
-Fail:\r
- lwFreeEnvelope( env );\r
- return NULL;\r
-}\r
-\r
-\r
-/*\r
-======================================================================\r
-lwFindEnvelope()\r
-\r
-Returns an lwEnvelope pointer, given an envelope index.\r
-====================================================================== */\r
-\r
-lwEnvelope *lwFindEnvelope( lwEnvelope *list, int index )\r
-{\r
- lwEnvelope *env;\r
-\r
- env = list;\r
- while ( env ) {\r
- if ( env->index == index ) break;\r
- env = env->next;\r
- }\r
- return env;\r
-}\r
-\r
-\r
-/*\r
-======================================================================\r
-range()\r
-\r
-Given the value v of a periodic function, returns the equivalent value\r
-v2 in the principal interval [lo, hi]. If i isn't NULL, it receives\r
-the number of wavelengths between v and v2.\r
-\r
- v2 = v - i * (hi - lo)\r
-\r
-For example, range( 3 pi, 0, 2 pi, i ) returns pi, with i = 1.\r
-====================================================================== */\r
-\r
-static float range( float v, float lo, float hi, int *i )\r
-{\r
- float v2, r = hi - lo;\r
-\r
- if ( r == 0.0 ) {\r
- if ( i ) *i = 0;\r
- return lo;\r
- }\r
-\r
- v2 = lo + v - r * ( float ) floor(( double ) v / r );\r
- if ( i ) *i = -( int )(( v2 - v ) / r + ( v2 > v ? 0.5 : -0.5 ));\r
-\r
- return v2;\r
-}\r
-\r
-\r
-/*\r
-======================================================================\r
-hermite()\r
-\r
-Calculate the Hermite coefficients.\r
-====================================================================== */\r
-\r
-static void hermite( float t, float *h1, float *h2, float *h3, float *h4 )\r
-{\r
- float t2, t3;\r
-\r
- t2 = t * t;\r
- t3 = t * t2;\r
-\r
- *h2 = 3.0f * t2 - t3 - t3;\r
- *h1 = 1.0f - *h2;\r
- *h4 = t3 - t2;\r
- *h3 = *h4 - t2 + t;\r
-}\r
-\r
-\r
-/*\r
-======================================================================\r
-bezier()\r
-\r
-Interpolate the value of a 1D Bezier curve.\r
-====================================================================== */\r
-\r
-static float bezier( float x0, float x1, float x2, float x3, float t )\r
-{\r
- float a, b, c, t2, t3;\r
-\r
- t2 = t * t;\r
- t3 = t2 * t;\r
-\r
- c = 3.0f * ( x1 - x0 );\r
- b = 3.0f * ( x2 - x1 ) - c;\r
- a = x3 - x0 - c - b;\r
-\r
- return a * t3 + b * t2 + c * t + x0;\r
-}\r
-\r
-\r
-/*\r
-======================================================================\r
-bez2_time()\r
-\r
-Find the t for which bezier() returns the input time. The handle\r
-endpoints of a BEZ2 curve represent the control points, and these have\r
-(time, value) coordinates, so time is used as both a coordinate and a\r
-parameter for this curve type.\r
-====================================================================== */\r
-\r
-static float bez2_time( float x0, float x1, float x2, float x3, float time,\r
- float *t0, float *t1 )\r
-{\r
- float v, t;\r
-\r
- t = *t0 + ( *t1 - *t0 ) * 0.5f;\r
- v = bezier( x0, x1, x2, x3, t );\r
- if ( fabs( time - v ) > .0001f ) {\r
- if ( v > time )\r
- *t1 = t;\r
- else\r
- *t0 = t;\r
- return bez2_time( x0, x1, x2, x3, time, t0, t1 );\r
- }\r
- else\r
- return t;\r
-}\r
-\r
-\r
-/*\r
-======================================================================\r
-bez2()\r
-\r
-Interpolate the value of a BEZ2 curve.\r
-====================================================================== */\r
-\r
-static float bez2( lwKey *key0, lwKey *key1, float time )\r
-{\r
- float x, y, t, t0 = 0.0f, t1 = 1.0f;\r
-\r
- if ( key0->shape == ID_BEZ2 )\r
- x = key0->time + key0->param[ 2 ];\r
- else\r
- x = key0->time + ( key1->time - key0->time ) / 3.0f;\r
-\r
- t = bez2_time( key0->time, x, key1->time + key1->param[ 0 ], key1->time,\r
- time, &t0, &t1 );\r
-\r
- if ( key0->shape == ID_BEZ2 )\r
- y = key0->value + key0->param[ 3 ];\r
- else\r
- y = key0->value + key0->param[ 1 ] / 3.0f;\r
-\r
- return bezier( key0->value, y, key1->param[ 1 ] + key1->value, key1->value, t );\r
-}\r
-\r
-\r
-/*\r
-======================================================================\r
-outgoing()\r
-\r
-Return the outgoing tangent to the curve at key0. The value returned\r
-for the BEZ2 case is used when extrapolating a linear pre behavior and\r
-when interpolating a non-BEZ2 span.\r
-====================================================================== */\r
-\r
-static float outgoing( lwKey *key0, lwKey *key1 )\r
-{\r
- float a, b, d, t, out;\r
-\r
- switch ( key0->shape )\r
- {\r
- case ID_TCB:\r
- a = ( 1.0f - key0->tension )\r
- * ( 1.0f + key0->continuity )\r
- * ( 1.0f + key0->bias );\r
- b = ( 1.0f - key0->tension )\r
- * ( 1.0f - key0->continuity )\r
- * ( 1.0f - key0->bias );\r
- d = key1->value - key0->value;\r
-\r
- if ( key0->prev ) {\r
- t = ( key1->time - key0->time ) / ( key1->time - key0->prev->time );\r
- out = t * ( a * ( key0->value - key0->prev->value ) + b * d );\r
- }\r
- else\r
- out = b * d;\r
- break;\r
-\r
- case ID_LINE:\r
- d = key1->value - key0->value;\r
- if ( key0->prev ) {\r
- t = ( key1->time - key0->time ) / ( key1->time - key0->prev->time );\r
- out = t * ( key0->value - key0->prev->value + d );\r
- }\r
- else\r
- out = d;\r
- break;\r
-\r
- case ID_BEZI:\r
- case ID_HERM:\r
- out = key0->param[ 1 ];\r
- if ( key0->prev )\r
- out *= ( key1->time - key0->time ) / ( key1->time - key0->prev->time );\r
- break;\r
-\r
- case ID_BEZ2:\r
- out = key0->param[ 3 ] * ( key1->time - key0->time );\r
- if ( fabs( key0->param[ 2 ] ) > 1e-5f )\r
- out /= key0->param[ 2 ];\r
- else\r
- out *= 1e5f;\r
- break;\r
-\r
- case ID_STEP:\r
- default:\r
- out = 0.0f;\r
- break;\r
- }\r
-\r
- return out;\r
-}\r
-\r
-\r
-/*\r
-======================================================================\r
-incoming()\r
-\r
-Return the incoming tangent to the curve at key1. The value returned\r
-for the BEZ2 case is used when extrapolating a linear post behavior.\r
-====================================================================== */\r
-\r
-static float incoming( lwKey *key0, lwKey *key1 )\r
-{\r
- float a, b, d, t, in;\r
-\r
- switch ( key1->shape )\r
- {\r
- case ID_LINE:\r
- d = key1->value - key0->value;\r
- if ( key1->next ) {\r
- t = ( key1->time - key0->time ) / ( key1->next->time - key0->time );\r
- in = t * ( key1->next->value - key1->value + d );\r
- }\r
- else\r
- in = d;\r
- break;\r
-\r
- case ID_TCB:\r
- a = ( 1.0f - key1->tension )\r
- * ( 1.0f - key1->continuity )\r
- * ( 1.0f + key1->bias );\r
- b = ( 1.0f - key1->tension )\r
- * ( 1.0f + key1->continuity )\r
- * ( 1.0f - key1->bias );\r
- d = key1->value - key0->value;\r
-\r
- if ( key1->next ) {\r
- t = ( key1->time - key0->time ) / ( key1->next->time - key0->time );\r
- in = t * ( b * ( key1->next->value - key1->value ) + a * d );\r
- }\r
- else\r
- in = a * d;\r
- break;\r
-\r
- case ID_BEZI:\r
- case ID_HERM:\r
- in = key1->param[ 0 ];\r
- if ( key1->next )\r
- in *= ( key1->time - key0->time ) / ( key1->next->time - key0->time );\r
- break;\r
- return in;\r
-\r
- case ID_BEZ2:\r
- in = key1->param[ 1 ] * ( key1->time - key0->time );\r
- if ( fabs( key1->param[ 0 ] ) > 1e-5f )\r
- in /= key1->param[ 0 ];\r
- else\r
- in *= 1e5f;\r
- break;\r
-\r
- case ID_STEP:\r
- default:\r
- in = 0.0f;\r
- break;\r
- }\r
-\r
- return in;\r
-}\r
-\r
-\r
-/*\r
-======================================================================\r
-evalEnvelope()\r
-\r
-Given a list of keys and a time, returns the interpolated value of the\r
-envelope at that time.\r
-====================================================================== */\r
-\r
-float evalEnvelope( lwEnvelope *env, float time )\r
-{\r
- lwKey *key0, *key1, *skey, *ekey;\r
- float t, h1, h2, h3, h4, in, out, offset = 0.0f;\r
- int noff;\r
-\r
-\r
- /* if there's no key, the value is 0 */\r
-\r
- if ( env->nkeys == 0 ) return 0.0f;\r
-\r
- /* if there's only one key, the value is constant */\r
-\r
- if ( env->nkeys == 1 )\r
- return env->key->value;\r
-\r
- /* find the first and last keys */\r
-\r
- skey = ekey = env->key;\r
- while ( ekey->next ) ekey = ekey->next;\r
-\r
- /* use pre-behavior if time is before first key time */\r
-\r
- if ( time < skey->time ) {\r
- switch ( env->behavior[ 0 ] )\r
- {\r
- case BEH_RESET:\r
- return 0.0f;\r
-\r
- case BEH_CONSTANT:\r
- return skey->value;\r
-\r
- case BEH_REPEAT:\r
- time = range( time, skey->time, ekey->time, NULL );\r
- break;\r
-\r
- case BEH_OSCILLATE:\r
- time = range( time, skey->time, ekey->time, &noff );\r
- if ( noff % 2 )\r
- time = ekey->time - skey->time - time;\r
- break;\r
-\r
- case BEH_OFFSET:\r
- time = range( time, skey->time, ekey->time, &noff );\r
- offset = noff * ( ekey->value - skey->value );\r
- break;\r
-\r
- case BEH_LINEAR:\r
- out = outgoing( skey, skey->next )\r
- / ( skey->next->time - skey->time );\r
- return out * ( time - skey->time ) + skey->value;\r
- }\r
- }\r
-\r
- /* use post-behavior if time is after last key time */\r
-\r
- else if ( time > ekey->time ) {\r
- switch ( env->behavior[ 1 ] )\r
- {\r
- case BEH_RESET:\r
- return 0.0f;\r
-\r
- case BEH_CONSTANT:\r
- return ekey->value;\r
-\r
- case BEH_REPEAT:\r
- time = range( time, skey->time, ekey->time, NULL );\r
- break;\r
-\r
- case BEH_OSCILLATE:\r
- time = range( time, skey->time, ekey->time, &noff );\r
- if ( noff % 2 )\r
- time = ekey->time - skey->time - time;\r
- break;\r
-\r
- case BEH_OFFSET:\r
- time = range( time, skey->time, ekey->time, &noff );\r
- offset = noff * ( ekey->value - skey->value );\r
- break;\r
-\r
- case BEH_LINEAR:\r
- in = incoming( ekey->prev, ekey )\r
- / ( ekey->time - ekey->prev->time );\r
- return in * ( time - ekey->time ) + ekey->value;\r
- }\r
- }\r
-\r
- /* get the endpoints of the interval being evaluated */\r
-\r
- key0 = env->key;\r
- while ( time > key0->next->time )\r
- key0 = key0->next;\r
- key1 = key0->next;\r
-\r
- /* check for singularities first */\r
-\r
- if ( time == key0->time )\r
- return key0->value + offset;\r
- else if ( time == key1->time )\r
- return key1->value + offset;\r
-\r
- /* get interval length, time in [0, 1] */\r
-\r
- t = ( time - key0->time ) / ( key1->time - key0->time );\r
-\r
- /* interpolate */\r
-\r
- switch ( key1->shape )\r
- {\r
- case ID_TCB:\r
- case ID_BEZI:\r
- case ID_HERM:\r
- out = outgoing( key0, key1 );\r
- in = incoming( key0, key1 );\r
- hermite( t, &h1, &h2, &h3, &h4 );\r
- return h1 * key0->value + h2 * key1->value + h3 * out + h4 * in + offset;\r
-\r
- case ID_BEZ2:\r
- return bez2( key0, key1, time ) + offset;\r
-\r
- case ID_LINE:\r
- return key0->value + t * ( key1->value - key0->value ) + offset;\r
-\r
- case ID_STEP:\r
- return key0->value + offset;\r
-\r
- default:\r
- return offset;\r
- }\r
-}\r
+/*
+======================================================================
+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;
+ }
+}