/*
    Apophysis Plugin - Synth v2

    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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/*
    The Thing That Should Not Be

    A much neater way of providing the kind of felixibility and control
    in Apophysis that I am aiming for with Synth would be to create
    a node-based system of connected functions that allowed advanced users
    to build their own routines for the Apophysis engine from simple
    components.

    Many other graphic and audio engines use this concept very successfully.

    Such a system could make FX, PX, xaos etc redundant too.

    I've made Synth, because I cannot forsee that kind of development happening
    with Apophysis. This is no sleight to the Apo developers - from Mark Townsend
    onwards the project has been an inspiration and joy to many people. It is
    more simply a recognition of how hard it would be to make such a change
    to Apophysis as it stands.
*/

// Must define this structure before we include apoplugin.h
typedef struct
{
    double synth_a;
	int synth_mode;
	double synth_power;
	double synth_mix;
	int synth_smooth;

    double synth_b;
    int synth_b_type;
    double synth_b_frq;
    double synth_b_skew;
    double synth_b_phs;
    int synth_b_layer;

    double synth_c;
    int synth_c_type;
    double synth_c_frq;
    double synth_c_skew;
    double synth_c_phs;
    int synth_c_layer;

    double synth_d;
    int synth_d_type;
    double synth_d_frq;
    double synth_d_skew;
    double synth_d_phs;
    int synth_d_layer;

    double synth_e;
    int synth_e_type;
    double synth_e_frq;
    double synth_e_skew;
    double synth_e_phs;
    int synth_e_layer;

    double synth_f;
    int synth_f_type;
    double synth_f_frq;
    double synth_f_skew;
    double synth_f_phs;
    int synth_f_layer;
} Variables;

#define _USE_MATH_DEFINES
#include "apoplugin.h"

// Set the name of this plugin
APO_PLUGIN("synth");

// Define the Variables
APO_VARIABLES(

    VAR_REAL(synth_a, 1.0),
    VAR_INTEGER(synth_mode, 3),
    VAR_REAL(synth_power, -2.0),
    VAR_REAL(synth_mix, 1.0),
    VAR_INTEGER(synth_smooth, 0),

    VAR_REAL(synth_b, 0.0),
    VAR_INTEGER(synth_b_type, 0),
    VAR_REAL(synth_b_skew, 0.0),
    VAR_REAL(synth_b_frq, 1.0),
    VAR_REAL(synth_b_phs, 0.0),
    VAR_INTEGER(synth_b_layer, 0),

    VAR_REAL(synth_c, 0.0),
    VAR_INTEGER(synth_c_type, 0),
    VAR_REAL(synth_c_skew, 0.0),
    VAR_REAL(synth_c_frq, 1.0),
    VAR_REAL(synth_c_phs, 0.0),
    VAR_INTEGER(synth_c_layer, 0),

    VAR_REAL(synth_d, 0.0),
    VAR_INTEGER(synth_d_type, 0),
    VAR_REAL(synth_d_skew, 0.0),
    VAR_REAL(synth_d_frq, 1.0),
    VAR_REAL(synth_d_phs, 0.0),
    VAR_INTEGER(synth_d_layer, 0),

    VAR_REAL(synth_e, 0.0),
    VAR_INTEGER(synth_e_type, 0),
    VAR_REAL(synth_e_skew, 0.0),
    VAR_REAL(synth_e_frq, 1.0),
    VAR_REAL(synth_e_phs, 0.0),
    VAR_INTEGER(synth_e_layer, 0),

	VAR_REAL(synth_f, 0.0),
	VAR_INTEGER(synth_f_type, 0),
	VAR_REAL(synth_f_skew, 0.0),
	VAR_REAL(synth_f_frq, 1.0),
	VAR_REAL(synth_f_phs, 0.0),
    VAR_INTEGER(synth_f_layer, 0)

);

// You must call the argument "vp".
int PluginVarPrepare(Variation* vp)
{
    return TRUE; // Always return TRUE.
}

// -------------------------------------------------------------
// Modes
// "Lagacy" modes from v1
#define MODE_SPHERICAL 0
#define MODE_BUBBLE 1
#define MODE_BLUR_LEGACY 2
// New modes in v2
#define MODE_BLUR_NEW 3
#define MODE_BLUR_ZIGZAG 4
#define MODE_RAWCIRCLE 5
#define MODE_RAWX 6
#define MODE_RAWY 7
#define MODE_RAWXY 8
#define MODE_SHIFTX 9
#define MODE_SHIFTY 10
#define MODE_SHIFTXY 11
#define MODE_SINUSOIDAL 12
#define MODE_SWIRL 13
#define MODE_HYPERBOLIC 14
#define MODE_JULIA 15
#define MODE_DISC 16
#define MODE_RINGS 17
#define MODE_CYLINDER 18
#define MODE_BLUR_RING 19
#define MODE_BLUR_RING2 20
#define MODE_SHIFTTHETA 21

// -------------------------------------------------------------
// Wave types
#define WAVE_SIN 0
#define WAVE_COS 1
#define WAVE_SQUARE 2
#define WAVE_SAW 3
#define WAVE_TRIANGLE 4
#define WAVE_CONCAVE 5
#define WAVE_CONVEX 6
#define WAVE_NGON 7
// New wave types in v2
#define WAVE_INGON 8

// -------------------------------------------------------------
// Layer types
#define LAYER_ADD 0
#define LAYER_MULT 1
#define LAYER_MAX 2
#define LAYER_MIN 3

// -------------------------------------------------------------
// Interpolation types
#define LERP_LINEAR 0
#define LERP_BEZIER 1

// -------------------------------------------------------------
// Sine/Cosine interpretation types
#define SINCOS_MULTIPLY 0
#define SINCOS_MIXIN 1

// -------------------------------------------------------------
// synth_value calculates the wave height y from theta, which is an abstract
// angle that could come from any other calculation - for circular modes
// it will be the angle between the positive y axis and the vector from
// the origin to the pont i.e. atan2(x,y)
// You must call the argument "vp".
inline double synth_value(Variation* vp, double theta)
{
    double theta_factor = VAR(synth_a);
    double x,y,z;

    if ( VAR(synth_b) != 0.0 ) {

		z = VAR(synth_b_phs) + theta * VAR(synth_b_frq);
		y = z / ( 2 * M_PI );
		y -= floor( y );

		// y is in range 0 - 1. Now skew according to synth_b_skew
		if (  VAR(synth_b_skew) != 0.0 ) {
			z = 0.5 + 0.5 * VAR(synth_b_skew);
			if ( y > z ) {
				// y is 0.5 if equals z, up to 1.0
				y = 0.5 + 0.5 * (y - z)/(1.0 - z + EPS);
			}
			else {
				// y is 0.5 if equals z, down to 0.0
				y = 0.5 - 0.5 * (z - y)/(z + EPS);
			}
		}

		switch ( VAR(synth_b_type) ) {
			case WAVE_SIN:
				x = sin( y * 2 * M_PI );
				break;
			case WAVE_COS:
				x = cos( y * 2 * M_PI );
				break;
			case WAVE_SQUARE:
				x = y > 0.5 ? 1.0 : -1.0;
				break;
			case WAVE_SAW:
				x = 1.0 - 2.0 * y;
				break;
			case WAVE_TRIANGLE:
				x = y > 0.5 ? 3.0 - 4.0 * y : 2.0 * y - 1.0;
				break;
			case WAVE_CONCAVE:
                x = 8.0 * ( y - 0.5 ) * ( y - 0.5 ) - 1.0;
				break;
			case WAVE_CONVEX:
                x = 2.0 * sqrt( y ) - 1.0;
				break;
			case WAVE_NGON:
                y -= 0.5;
                y *= (2.0 * M_PI / VAR(synth_b_frq) );
                x = ( 1.0 / ( cos(y) + EPS )  - 1.0);
				break;
			case WAVE_INGON:
                y -= 0.5;
                y *= (2.0 * M_PI / VAR(synth_b_frq) );
                z = cos(y);
                x = z / ( 1.0 + EPS - z );
				break;
		}

		switch ( VAR(synth_b_layer) ) {
			case LAYER_ADD:
				theta_factor += VAR(synth_b) * x;
				break;
		    case LAYER_MULT:
				theta_factor *= ( 1.0 + VAR(synth_b) * x );
				break;
			case LAYER_MAX:
			    z = VAR(synth_a) + VAR(synth_b) * x;
			    theta_factor = ( theta_factor > z ? theta_factor : z );
			    break;
			case LAYER_MIN:
			    z = VAR(synth_a) + VAR(synth_b) * x;
			    theta_factor = ( theta_factor < z ? theta_factor : z );
			    break;
		}
	}


    if ( VAR(synth_c) != 0.0 ) {

		z = VAR(synth_c_phs) + theta * VAR(synth_c_frq);
		y = z / ( 2 * M_PI );
		y -= floor( y );

		// y is in range 0 - 1. Now skew according to synth_c_skew
		if (  VAR(synth_c_skew) != 0.0 ) {
			z = 0.5 + 0.5 * VAR(synth_c_skew);
			if ( y > z ) {
				// y is 0.5 if equals z, up to 1.0
				y = 0.5 + 0.5 * (y - z)/(1.0 - z + EPS);
			}
			else {
				// y is 0.5 if equals z, down to 0.0
				y = 0.5 - 0.5 * (z - y)/(z + EPS);
			}
		}

		switch ( VAR(synth_c_type) ) {
			case WAVE_SIN:
				x = sin( y * 2 * M_PI );
				break;
			case WAVE_COS:
				x = cos( y * 2 * M_PI );
				break;
			case WAVE_SQUARE:
				x = y > 0.5 ? 1.0 : -1.0;
				break;
			case WAVE_SAW:
				x = 1.0 - 2.0 * y;
				break;
			case WAVE_TRIANGLE:
				x = y > 0.5 ? 3.0 - 4.0 * y : 2.0 * y - 1.0;
				break;
			case WAVE_CONCAVE:
                x = 8.0 * ( y - 0.5 ) * ( y - 0.5 ) - 1.0;
				break;
			case WAVE_CONVEX:
                x = 2.0 * sqrt( y ) - 1.0;
				break;
			case WAVE_NGON:
                y -= 0.5;
                y *= (2.0 * M_PI / VAR(synth_c_frq) );
                x = ( 1.0 / ( cos(y) + EPS )  - 1.0);
				break;
			case WAVE_INGON:
                y -= 0.5;
                y *= (2.0 * M_PI / VAR(synth_c_frq) );
                z = cos(y);
                x = z / ( 1.0 + EPS - z );
				break;
		}

		switch ( VAR(synth_c_layer) ) {
			case LAYER_ADD:
				theta_factor += VAR(synth_c) * x;
				break;
		    case LAYER_MULT:
				theta_factor *= ( 1.0 + VAR(synth_c) * x );
				break;
			case LAYER_MAX:
			    z = VAR(synth_a) + VAR(synth_c) * x;
			    theta_factor = ( theta_factor > z ? theta_factor : z );
			    break;
			case LAYER_MIN:
			    z = VAR(synth_a) + VAR(synth_c) * x;
			    theta_factor = ( theta_factor < z ? theta_factor : z );
			    break;
		}
	}


    if ( VAR(synth_d) != 0.0 ) {

		z = VAR(synth_d_phs) + theta * VAR(synth_d_frq);
		y = z / ( 2 * M_PI );
		y -= floor( y );

		// y is in range 0 - 1. Now skew according to synth_d_skew
		if (  VAR(synth_d_skew) != 0.0 ) {
			z = 0.5 + 0.5 * VAR(synth_d_skew);
			if ( y > z ) {
				// y is 0.5 if equals z, up to 1.0
				y = 0.5 + 0.5 * (y - z)/(1.0 - z + EPS);
			}
			else {
				// y is 0.5 if equals z, down to 0.0
				y = 0.5 - 0.5 * (z - y)/(z + EPS);
			}
		}

		switch ( VAR(synth_d_type) ) {
			case WAVE_SIN:
				x = sin( y * 2 * M_PI );
				break;
			case WAVE_COS:
				x = cos( y * 2 * M_PI );
				break;
			case WAVE_SQUARE:
				x = y > 0.5 ? 1.0 : -1.0;
				break;
			case WAVE_SAW:
				x = 1.0 - 2.0 * y;
				break;
			case WAVE_TRIANGLE:
				x = y > 0.5 ? 3.0 - 4.0 * y : 2.0 * y - 1.0;
				break;
			case WAVE_CONCAVE:
                x = 8.0 * ( y - 0.5 ) * ( y - 0.5 ) - 1.0;
				break;
			case WAVE_CONVEX:
                x = 2.0 * sqrt( y ) - 1.0;
				break;
			case WAVE_NGON:
                y -= 0.5;
                y *= (2.0 * M_PI / VAR(synth_d_frq) );
                x = ( 1.0 / ( cos(y) + EPS )  - 1.0);
				break;
			case WAVE_INGON:
                y -= 0.5;
                y *= (2.0 * M_PI / VAR(synth_d_frq) );
                z = cos(y);
                x = z / ( 1.0 + EPS - z );
				break;
		}

		switch ( VAR(synth_d_layer) ) {
			case LAYER_ADD:
				theta_factor += VAR(synth_d) * x;
				break;
		    case LAYER_MULT:
				theta_factor *= ( 1.0 + VAR(synth_d) * x );
				break;
			case LAYER_MAX:
			    z = VAR(synth_a) + VAR(synth_d) * x;
			    theta_factor = ( theta_factor > z ? theta_factor : z );
			    break;
			case LAYER_MIN:
			    z = VAR(synth_a) + VAR(synth_d) * x;
			    theta_factor = ( theta_factor < z ? theta_factor : z );
			    break;
		}
	}


    if ( VAR(synth_e) != 0.0 ) {

		z = VAR(synth_e_phs) + theta * VAR(synth_e_frq);
		y = z / ( 2 * M_PI );
		y -= floor( y );

		// y is in range 0 - 1. Now skew according to synth_e_skew
		if (  VAR(synth_e_skew) != 0.0 ) {
			z = 0.5 + 0.5 * VAR(synth_e_skew);
			if ( y > z ) {
				// y is 0.5 if equals z, up to 1.0
				y = 0.5 + 0.5 * (y - z)/(1.0 - z + EPS);
			}
			else {
				// y is 0.5 if equals z, down to 0.0
				y = 0.5 - 0.5 * (z - y)/(z + EPS);
			}
		}

		switch ( VAR(synth_e_type) ) {
			case WAVE_SIN:
				x = sin( y * 2 * M_PI );
				break;
			case WAVE_COS:
				x = cos( y * 2 * M_PI );
				break;
			case WAVE_SQUARE:
				x = y > 0.5 ? 1.0 : -1.0;
				break;
			case WAVE_SAW:
				x = 1.0 - 2.0 * y;
				break;
			case WAVE_TRIANGLE:
				x = y > 0.5 ? 3.0 - 4.0 * y : 2.0 * y - 1.0;
				break;
			case WAVE_CONCAVE:
                x = 8.0 * ( y - 0.5 ) * ( y - 0.5 ) - 1.0;
				break;
			case WAVE_CONVEX:
                x = 2.0 * sqrt( y ) - 1.0;
				break;
			case WAVE_NGON:
                y -= 0.5;
                y *= (2.0 * M_PI / VAR(synth_e_frq) );
                x = ( 1.0 / ( cos(y) + EPS )  - 1.0);
				break;
			case WAVE_INGON:
                y -= 0.5;
                y *= (2.0 * M_PI / VAR(synth_e_frq) );
                z = cos(y);
                x = z / ( 1.0 + EPS - z );
				break;

		}

		switch ( VAR(synth_e_layer) ) {
			case LAYER_ADD:
				theta_factor += VAR(synth_e) * x;
				break;
		    case LAYER_MULT:
				theta_factor *= ( 1.0 + VAR(synth_e) * x );
				break;
			case LAYER_MAX:
			    z = VAR(synth_a) + VAR(synth_e) * x;
			    theta_factor = ( theta_factor > z ? theta_factor : z );
			    break;
			case LAYER_MIN:
			    z = VAR(synth_a) + VAR(synth_e) * x;
			    theta_factor = ( theta_factor < z ? theta_factor : z );
			    break;
		}
	}


    if ( VAR(synth_f) != 0.0 ) {

		z = VAR(synth_f_phs) + theta * VAR(synth_f_frq);
		y = z / ( 2 * M_PI );
		y -= floor( y );

		// y is in range 0 - 1. Now skew according to synth_f_skew
		if (  VAR(synth_f_skew) != 0.0 ) {
			z = 0.5 + 0.5 * VAR(synth_f_skew);
			if ( y > z ) {
				// y is 0.5 if equals z, up to 1.0
				y = 0.5 + 0.5 * (y - z)/(1.0 - z + EPS);
			}
			else {
				// y is 0.5 if equals z, down to 0.0
				y = 0.5 - 0.5 * (z - y)/(z + EPS);
			}
		}

		switch ( VAR(synth_f_type) ) {
			case WAVE_SIN:
				x = sin( y * 2 * M_PI );
				break;
			case WAVE_COS:
				x = cos( y * 2 * M_PI );
				break;
			case WAVE_SQUARE:
				x = y > 0.5 ? 1.0 : -1.0;
				break;
			case WAVE_SAW:
				x = 1.0 - 2.0 * y;
				break;
			case WAVE_TRIANGLE:
				x = y > 0.5 ? 3.0 - 4.0 * y : 2.0 * y - 1.0;
				break;
			case WAVE_CONCAVE:
                x = 8.0 * ( y - 0.5 ) * ( y - 0.5 ) - 1.0;
				break;
			case WAVE_CONVEX:
                x = 2.0 * sqrt( y ) - 1.0;
				break;
			case WAVE_NGON:
                y -= 0.5;
                y *= (2.0 * M_PI / VAR(synth_f_frq) );
                x = ( 1.0 / ( cos(y) + EPS )  - 1.0);
				break;
			case WAVE_INGON:
                y -= 0.5;
                y *= (2.0 * M_PI / VAR(synth_f_frq) );
                z = cos(y);
                x = z / ( 1.0 + EPS - z );
				break;
		}

		switch ( VAR(synth_f_layer) ) {
			case LAYER_ADD:
				theta_factor += VAR(synth_f) * x;
				break;
		    case LAYER_MULT:
				theta_factor *= ( 1.0 + VAR(synth_f) * x );
				break;
			case LAYER_MAX:
			    z = VAR(synth_a) + VAR(synth_f) * x;
			    theta_factor = ( theta_factor > z ? theta_factor : z );
			    break;
			case LAYER_MIN:
			    z = VAR(synth_a) + VAR(synth_f) * x;
			    theta_factor = ( theta_factor < z ? theta_factor : z );
			    break;
		}
	}

    // Mix is applied here, assuming 1.0 to be the "flat" line for legacy support
    return theta_factor * VAR(synth_mix) + ( 1.0 - VAR(synth_mix) );
}

// -------------------------------------------------------------
// Mapping function y = fn(x) based on quadratic Bezier curves for smooth type 1
// Returns close to y = x for high/low values of x, y = m when x = 1.0, and
// something in-between y = m*x and y = x lines when x is close-ish to 1.0
// Function always has slope of 0.0 or greater, so no x' values "overlap"
inline double bezier_quad_map( double x, double m )
{
	double a = 1.0; // a is used to control sign of result
	double t = 0.0; // t is the Bezier curve parameter

	// Simply reflect in the y axis for negative values
	if ( m < 0.0 ) { m = -m; a = -1.0; }
	if ( x < 0.0 ) { x = -x; a = -a; }

	// iM is "inverse m" used in a few places below
	double iM = 1e10;
	if ( m > 1.0e-10 )
	{
       iM = 1.0 / m;
    }

    // L is the upper bound on our curves, where we have rejoined the y = x line
    double L = iM < m * 2.0 ? m * 2.0 : iM;

	// "Non Curved"
	// Covers x >= L, or always true if m == 1.0
	// y = x  i.e. not distorted
	if ( ( x > L ) || ( m == 1.0 ) )
	{
		return a * x;
	}

	if ( ( m < 1.0 ) && ( x <= 1.0 ) )
	{
		// Bezier Curve #1
		// Covers 0 <= $m <= 1.0, 0 <= $x <= 1.0
		// Control points are (0,0), (m,m) and (1,m)

		t = x; // Special case when m == 0.5
		if ( abs(m-0.5) > 1e-10 )
		{
			t = ( -1.0 * m + sqrt(  m * m + ( 1.0 - 2.0 * m) * x ) ) / ( 1.0 - 2.0 * m );
		}
		return a * ( x + ( m - 1.0 ) * t * t );
	}

	if  ( ( 1.0 < m ) && ( x <= 1.0 ) )
	{
		// Bezier Curve #2
		// Covers m >= 1.0, 0 <= x <= 1.0
		// Control points are (0,0), (iM,iM) and (1,m)

		t = x; // Special case when m == 2
		if ( abs(m-2.0) > 1e-10 )
		{
			t = ( -1.0 * iM + sqrt( iM * iM + ( 1.0 - 2.0 * iM ) * x ) ) / ( 1 - 2 * iM );
		}
		return a * ( x + ( m - 1.0 ) * t * t );
	}

	if ( m < 1.0 )
	{
		// Bezier Curve #3
		// Covers 0 <= m <= 1.0, 1 <= x <= L
		// Control points are (1,m), (1,1) and (L,L)
		// (L is x value (>1) where we re-join y = x line, and is maximum( iM, 2 * m )

		t =	sqrt( ( x - 1.0 ) / ( L - 1.0 ) );
		return a * ( x  +  ( m - 1.0 ) * t * t  +  2 * ( 1.0 - m ) * t  + ( m - 1.0 ) );
	}

	// Curve #4
	// Covers 1.0 <= m, 1 <= x <= L
	// Control points are (1,m), (m,m) and (L,L)
	// (L is x value (>1) where we re-join y = x line, and is maximum( iM, 2 *  m )

	t =	( 1.0 - m ) + sqrt( ( m - 1.0 ) * ( m - 1.0 ) + ( x - 1.0 ) );
	return a * ( x + ( m - 1.0 ) * t * t - 2.0 * ( m - 1.0 ) *  t + (  m - 1.0 ) );
}


// Handle potentially many types of interpolation routine in future  . . .
inline double interpolate( double x, double m, int lerp_type )
{
    switch ( lerp_type )
    {
           case LERP_LINEAR:
                return x * m;
           case LERP_BEZIER:
                return bezier_quad_map( x, m );
    }
    return x * m;
}

inline void synthsincos( Variation* vp, double theta, double* s, double* c, int sine_type )
{
    fsincos( theta, s, c );
    switch ( sine_type )
    {
           case SINCOS_MULTIPLY:
                *s = (*s) * synth_value( vp, theta );
                *c = (*c) * synth_value( vp, theta + M_PI/2.0 );
                break;
           case SINCOS_MIXIN:
                *s = ( 1.0 - VAR(synth_mix) ) * (*s) + ( synth_value( vp, theta ) - 1.0 );
                *c = ( 1.0 - VAR(synth_mix) ) * (*c) + ( synth_value( vp, theta + M_PI/2.0 ) - 1.0 );
                break;
    }
    return;
}

// You must call the argument "vp".
int PluginVarCalc(Variation* vp)
{
    double Vx, Vy, radius, theta; // Position vector in cartesian and polar co-ords
    double theta_factor;          // Evaluation of synth() function for current point
    double s, c, mu;              // Handy temp variables, s & c => sine & cosine, mu = generic temp param

    switch( VAR(synth_mode) ) {

		case MODE_RAWCIRCLE:  // Power NO, Smooth YES
		    // Get current radius and angle
			Vx = FTx;
			Vy = FTy;
			radius = sqrt(Vx * Vx + Vy * Vy);
            theta = atan2(Vx, Vy);

            // Calculate new radius
            theta_factor = synth_value( vp, theta );
            radius = interpolate( radius, theta_factor, VAR(synth_smooth) );
            fsincos(theta, &s, &c);

            // Write to running totals for transform
            FPx += VVAR * radius * s;
            FPy += VVAR * radius * c;
			break;


		case MODE_RAWY:  // Power NO, Smooth YES
		    // Use x and y values directly
			Vx = FTx;
			Vy = FTy;

            // y value will be mapped according to synth(x) value
            theta_factor = synth_value( vp, Vx );

            // Write to running totals for transform
            FPx += VVAR * Vx;
            FPy += VVAR * interpolate( Vy, theta_factor, VAR(synth_smooth) );;
			break;


		case MODE_RAWX:  // Power NO, Smooth YES
		    // Use x and y values directly
			Vx = FTx;
			Vy = FTy;

            // x value will be mapped according to synth(y) value
            theta_factor = synth_value( vp, Vy );

            // Write to running totals for transform
            FPx += VVAR * interpolate( Vx, theta_factor, VAR(synth_smooth) );
            FPy += VVAR * Vy;
			break;


		case MODE_RAWXY:  // Power NO, Smooth YES
		    // Use x and y values directly
			Vx = FTx;
			Vy = FTy;

            // x value will be mapped according to synth(y) value
            theta_factor = synth_value( vp, Vy );
            FPx += VVAR * interpolate( Vx, theta_factor, VAR(synth_smooth) );

            // y value will be mapped according to synth(x) value
            theta_factor = synth_value( vp, Vx );
            FPy += VVAR * interpolate( Vy, theta_factor, VAR(synth_smooth) );
			break;


		case MODE_SPHERICAL:  // Power YES, Smooth YES
		    // Re-write of spherical with synth tweak
			Vx = FTx;
			Vy = FTy;
			radius = pow(Vx * Vx + Vy * Vy + EPS, ( VAR(synth_power) + 1.0 )/2.0);

            // Get angle and angular factor
            theta = atan2(Vx, Vy);
            theta_factor = synth_value( vp, theta );
            radius = interpolate( radius, theta_factor, VAR(synth_smooth) );
            fsincos(theta, &s, &c);

            // Write to running totals for transform
            FPx += VVAR * radius * s;
            FPy += VVAR * radius * c;
			break;


	    case MODE_BUBBLE:  // Power NO, Smooth YES
		    // Re-write of bubble with synth tweak
			Vx = FTx;
			Vy = FTy;
	        radius = sqrt(Vx * Vx + Vy * Vy) / ( (Vx * Vx + Vy * Vy)/4 + 1);

            // Get angle and angular factor
            theta = atan2(Vx, Vy);
            theta_factor = synth_value( vp, theta );
            radius = interpolate( radius, theta_factor, VAR(synth_smooth) );
            fsincos(theta, &s, &c);

            // Write to running totals for transform
            FPx += VVAR * radius * s;
            FPy += VVAR * radius * c;
			break;


	    case MODE_BLUR_LEGACY:  // Power YES, Smooth YES
	        // "old" blur style, has some problems with moire-style artefacts
			radius = ( random01() + random01() + 0.002 * random01() ) / 2.002;
			theta = 2.0 * M_PI * random01() - M_PI;
		    Vx = radius * sin(theta);
		    Vy = radius * cos(theta);
		    radius = pow( radius * radius + EPS, VAR(synth_power)/2.0);

            // Get angle and angular factor
            theta_factor = synth_value( vp, theta );
            radius = VVAR * interpolate( radius, theta_factor, VAR(synth_smooth) );

            // Write back to running totals for new vector
            FPx += Vx * radius;
            FPy += Vy * radius;
            break;


	    case MODE_BLUR_NEW:  // Power YES, Smooth YES
	        // Blur style, with normal smoothing function

            // Choose radius randomly, then adjust distribution using pow
            radius = 0.5 * ( random01() + random01() );
			theta = 2 * M_PI * random01() - M_PI;
		    radius = pow( radius * radius + EPS, - VAR(synth_power)/2.0 );

            // Get angular factor defining the shape
            theta_factor = synth_value( vp, theta );

            // Get final radius after synth applied
            radius = interpolate( radius, theta_factor, VAR(synth_smooth) );
            fsincos(theta, &s, &c);

            // Write to running totals for transform
            FPx += VVAR * radius * s;
            FPy += VVAR * radius * c;
            break;


	    case MODE_BLUR_RING:  // Power YES, Smooth YES
	        // Blur style, with normal smoothing function

            radius = 1.0 + 0.1 * ( random01() + random01() - 1.0 ) * VAR(synth_power);
			theta = 2 * M_PI * random01() - M_PI;

            // Get angular factor defining the shape
            theta_factor = synth_value( vp, theta );

            // Get final radius after synth applied
            radius = interpolate( radius, theta_factor, VAR(synth_smooth) );
            fsincos(theta, &s, &c);

            // Write to running totals for transform
            FPx += VVAR * radius * s;
            FPy += VVAR * radius * c;
            break;


	    case MODE_BLUR_RING2:  // Power YES, Smooth NO
	        // Simple, same-thickness ring

            // Choose radius randomly, then adjust distribution using pow
			theta = 2 * M_PI * random01() - M_PI;

            radius = pow( random01() + EPS, VAR(synth_power) );

            // Get final radius after synth applied
            radius = synth_value( vp, theta ) + 0.1 * radius;
            fsincos(theta, &s, &c);

            // Write to running totals for transform
            FPx += VVAR * radius * s;
            FPy += VVAR * radius * c;
            break;


	    case MODE_SHIFTTHETA:  // Power YES, Smooth NO
            // Use (adjusted) radius to move point around circle
            Vx = FTx;
            Vy = FTy;

            radius = pow(Vx * Vx + Vy * Vy + EPS, VAR(synth_power)/2.0 );

            theta  = atan2( Vx, Vy ) - 1.0 + synth_value( vp, radius );

            fsincos(theta, &s, &c);

            // Write to running totals for transform
            FPx += VVAR * radius * s;
            FPy += VVAR * radius * c;
            break;


    	case MODE_BLUR_ZIGZAG:  // Power YES, Smooth YES
		    // Blur effect based on line segment
            // theta is used as x value
            // Vy is y value
            Vy    = 1.0 + 0.1 * ( random01() + random01() - 1.0 ) * VAR(synth_power);
			theta = 2.0 * asin( (random01()- 0.5) * 2.0 );

            // Get angular factor defining the shape
            theta_factor = synth_value( vp, theta );

            // Get new location
            Vy = interpolate( Vy, theta_factor, VAR(synth_smooth) );

            // Write to running totals for transform
            FPx += VVAR * ( theta / M_PI );
            FPy += VVAR * ( Vy - 1.0 );
            break;


        case MODE_SHIFTX:  // Power NO, Smooth YES
		    // Use x and y values directly
			Vx = FTx;
			Vy = FTy;

            // Write to running totals for transform
            FPx += VVAR * ( Vx + synth_value( vp, Vy ) - 1.0);
            FPy += VVAR * Vy;
			break;


        case MODE_SHIFTY:  // Power NO, Smooth NO
		    // Use x and y values directly
			Vx = FTx;
			Vy = FTy;

            // Write to running totals for transform
            FPx += VVAR * Vx;
            FPy += VVAR * ( Vy + synth_value( vp, Vx ) - 1.0);

            break;


        case MODE_SHIFTXY:  // Power NO, Smooth NO
		    // Use x and y values directly
			Vx = FTx;
			Vy = FTy;

            // Write to running totals for transform
            FPx += VVAR * ( Vx + synth_value( vp, Vy ) - 1.0);
            FPy += VVAR * ( Vy + synth_value( vp, Vx ) - 1.0);

            break;


        case MODE_SINUSOIDAL:  // Power NO, Smooth NO
             Vx = FTx;
             Vy = FTy;

             // The default mix=0 is same as normal sin
             FPx +=  VVAR * ( synth_value( vp, Vx ) - 1.0 + (1.0-VAR(synth_mix)) * sin (Vx) );
             FPy +=  VVAR * ( synth_value( vp, Vy ) - 1.0 + (1.0-VAR(synth_mix)) * sin (Vy) );

             break;


        case MODE_SWIRL:  // Power YES, Smooth WAVE
             Vx = FTx;
             Vy = FTy;

             radius = pow( Vx * Vx + Vy * Vy + EPS, VAR(synth_power)/2.0 );

             // Synth-modified sine & cosine
             synthsincos( vp, radius, &s, &c, VAR(synth_smooth) );

             FPx +=  VVAR * (s * Vx - c * Vy);
	         FPy +=  VVAR * (c * Vx + s * Vy);

             break;


        case MODE_HYPERBOLIC:  // Power YES, Smooth WAVE
             Vx = FTx;
             Vy = FTy;

             radius = pow(Vx * Vx + Vy * Vy + EPS, VAR(synth_power)/2.0 );

             theta  = atan2( Vx, Vy );

             // Synth-modified sine & cosine
             synthsincos( vp, theta, &s, &c, VAR(synth_smooth) );

             FPx += VVAR * s / radius;
             FPy += VVAR * c * radius;

             break;


        case MODE_JULIA: // Power YES, Smooth WAVE
            Vx = FTx;
            Vy = FTy;

            radius = pow(Vx * Vx + Vy * Vy + EPS, VAR(synth_power)/4.0 );

            theta  = atan2( Vx, Vy )/2.0;

            if ( random01() < 0.5 ) theta += M_PI;

            // Synth-modified sine & cosine
            synthsincos( vp, theta, &s, &c, VAR(synth_smooth) );

            FPx += VVAR * radius * c;
            FPy += VVAR * radius * s;

            break;


        case MODE_DISC: // Power YES, Smooth WAVE
            Vx = FTx;
            Vy = FTy;

            theta = atan2( Vx, Vy ) / M_PI;

            radius  = M_PI * pow(Vx * Vx + Vy * Vy + EPS, VAR(synth_power)/2.0 );

            // Synth-modified sine & cosine
            synthsincos( vp, radius, &s, &c, VAR(synth_smooth) );

            FPx = VVAR * s * theta;
            FPy = VVAR * c * theta;

            break;


        case MODE_RINGS: // Power PARAM, Smooth WAVE

            Vx = FTx;
            Vy = FTy;
            radius = sqrt( Vx * Vx + Vy * Vy );
            theta = atan2( Vx, Vy );

            mu = VAR(synth_power) * VAR(synth_power) + EPS;

            radius += -2.0 * mu * (int)((radius + mu)/( 2.0 * mu )) + radius * ( 1.0 - mu );

            synthsincos( vp, radius, &s, &c, VAR(synth_smooth) );

            FPx += VVAR * s * radius;
            FPy += VVAR * c * radius;

            break;


        case MODE_CYLINDER: // Power YES, Smooth WAVE
            Vx = FTx;
            Vy = FTy;
            radius = pow(Vx * Vx + Vy * Vy + EPS, VAR(synth_power)/2.0 );

            // Modified sine only used here
            synthsincos( vp, Vx, &s, &c, VAR(synth_smooth) );

            FPx += VVAR * radius * s;
            FPy += VVAR * radius * Vy;

            break;
	}

    return TRUE;
}
