slider1:0<-100,100,1>Pitch Fine (+/-)
slider2:0<-12,12,1>Pitch Coarse (+/-)
slider3:12<0,100,1>Feedback (%)
slider4:12<0,100,1>Delay (ms)
slider5:0<0,3,1{Mono,Mono Wet Only,Stereo,Stereo Wet only}>Mix Mode
slider6:-3<-30,12,0.1>Wet (dB)
slider7:-30<-30,12,0.1>Dry (dB)
slider8:0<0,1,1{tanh,hard}>--Limit

in_pin:L in
in_pin:R in
out_pin:L out
out_pin:R out

@init
_feedbackPhase = 1;
_fp = 0; // fill/write pointer
_sweepSamples = 0; // samples to use in sweep
_sweepInc = 0; // calculated by desired pitch deviation

outval = 0; // most recent output value (for feedback)

_mixLeftWet =
_mixLeftDry =
_mixRightWet =
_mixRightDry = 0.5;

SEMITONE = 1.059463;
MIN_SWEEP = 0.015;
MAX_SWEEP = 0.1;
NUM_PITCHES = 25;
MIN_DELAY_SAMPLES = 22;

BSZ = 8192; // ~0.2*srate
//buffer = 128;
//memset(buffer, 0, BSZ);

function tanh(x) 
( 
x = exp(2*x);
(x - 1) / (x + 1);
);

@slider
_paramFinePitch = max(min((slider1+100)*0.005,1),0);
_paramCoarsePitch = max(min((slider2+13)*0.04,1),0.04);
_paramFeedback = max(min(slider3/200,0.5),0);
_paramDelay = slider4/100;

// delay
// convert incoming float to int sample count for up to 100ms of delay
_delaySamples = 0.1 * srate * _paramDelay|0;

// pin to a minimum value so our sweep never collides with the filling pointer
_delaySamples = _delaySamples < MIN_DELAY_SAMPLES ? MIN_DELAY_SAMPLES : _delaySamples;

// sweep
// calc the total pitch delta
semiTones = 13 - (_paramCoarsePitch * NUM_PITCHES);
fine = ((2 * _paramFinePitch) - 1) * -1;
pitchDelta = pow(SEMITONE,semiTones + fine);

// see if we're increasing or decreasing
_increasing = pitchDelta >= 1;

// calc the # of samples in the sweep, 15ms minimum, scaled up to 50ms for an octave up,
// and 32.5ms for an octave down
absDelta = abs(pitchDelta - 1);
_sweepSamples = (MIN_SWEEP + (MAX_SWEEP-MIN_SWEEP) * absDelta) * srate;

// fix up the pitchDelta to become the _sweepInc
_sweepInc = pitchDelta - 1;
_sweepInc = -_sweepInc;

// assign initial pointers
_sweepB = _sweepSamples / 2;
_sweepA = _increasing ? _sweepSamples : 0;

wet = slider6 <= -30 ? 0 : 10^(slider6/20);
dry = slider7 <= -30 ? 0 : 10^(slider7/20);

@sample
slider5 == 0 ? (
_mixLeftWet = _mixRightWet = 1;
_mixLeftDry = _mixRightDry = 1;
);

slider5 == 1 ? (
_mixLeftWet = _mixRightWet = 1;
_mixLeftDry = _mixRightDry = 0;
);

slider5 == 2 ? (
_mixLeftWet = 1;
_mixRightWet = -1;
_mixLeftDry = _mixRightDry = 1;
);

slider5 == 3 ? (
_mixLeftWet = 1;
_mixRightWet = -1;
_mixLeftDry = _mixRightDry = 0;
);

in0 = spl0;
in1 = spl1;

inval = (in0 + in1)*0.5;
inmix = inval + tanh(_paramFeedback * _feedbackPhase * outval);

buf[buffer+_fp] = inmix;
_fp = (_fp + 1) & (BSZ-1);

// do the two taps
outval = 0;

// channel A build the two emptying pointers and do linear interpolation
ep = _fp - _delaySamples;
ep -= _sweepA;
( ep < BSZ ) ? (
ep += BSZ;
);

ep1 = ep|0;
w2 = ep-ep1;

ep1 &= (BSZ-1);
ep2 = ep1 + 1;
ep2 &= (BSZ-1);
w1 = 1 - w2;
tapout = buf[buffer+ep1] * w1 + buf[buffer+ep2] * w2;
fade = sin((_sweepA / _sweepSamples) * $pi);
tapout *= fade;
outval += tapout;

// step the sweep
_sweepA += _sweepInc;
( _sweepA < 0 ) ? (
_sweepA = _sweepSamples;
) : ( _sweepA >= _sweepSamples) ? (
_sweepA = 0;
);

// channel B build the two emptying pointers and do linear interpolation
ep = _fp - _delaySamples;
ep -= _sweepB;
( ep < BSZ ) ? (
ep += BSZ;
);

ep1 = ep|0;
w2 = ep-ep1;

ep1 &= (BSZ-1);
ep2 = ep1 + 1;
ep2 &= (BSZ-1);
w1 = 1 - w2;
tapout = buf[buffer+ep1] * w1 + buf[buffer+ep2] * w2;
fade = sin((_sweepB / _sweepSamples) * $pi);
tapout *= fade;
outval += tapout;

// step the sweep
_sweepB += _sweepInc;
( _sweepB < 0 ) ? (
_sweepB = _sweepSamples;
) : ( _sweepB >= _sweepSamples) ? (
_sweepB = 0;
);

// develop output mix
slider8 ? (
out0 = min(max(_mixLeftDry * inval + _mixLeftWet * outval,-0.99), 0.99);
out1 = min(max(_mixRightDry * inval + _mixRightWet * outval,-0.99), 0.99);
):(
out0 = tanh(_mixLeftDry * inval + _mixLeftWet * outval);
out1 = tanh(_mixRightDry * inval + _mixRightWet * outval);
);

spl0 = in0*dry + out0*wet;
spl1 = in1*dry + out1*wet;

@gfx 0 26
gfx_x=gfx_y=10;
gfx_r=gfx_b=0;
gfx_g=gfx_a=1;
gfx_drawchar($'P');
gfx_drawchar($'i');
gfx_drawchar($'t');
gfx_drawchar($'c');
gfx_drawchar($'h');
gfx_drawchar($' ');
gfx_drawchar($'=');
gfx_drawchar($' ');
gfx_drawnumber(semitones+fine,2);
gfx_drawchar($' ');
gfx_drawchar($'S');
gfx_drawchar($'e');
gfx_drawchar($'m');
gfx_drawchar($'i');
gfx_drawchar($'t');
gfx_drawchar($'o');
gfx_drawchar($'n');
gfx_drawchar($'e');
gfx_drawchar($'s');
