slider1:_paramFinePitch=0<-100,100,1>Pitch Fine (+/-)
slider2:_paramCoarsePitch=0<-11,11,1>Pitch Coarse (+/-)
slider3:_paramFeedback=50<0,100,1>Feedback (%)
slider4:_paramDelay=12<0,100,1>Delay (ms)
slider5:_paramMix=0<0,7,1{Mono,Stereo,Mono Minus,Stereo Minus,Mono Wet Only,Stereo Wet Only,Mono Wet Only Minus,Stereo Wet Only Minus}>Mix Mode
slider6:0<0,100,1>Dry Mix (%)
slider7:0<-30,12,0.1>Output (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
ext_nodenorm = 1;

_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
buf = BSZ;
memset(buf,0,BSZ);

i=0;
while(i<BSZ) (
i+=1;
buf[i]=0;
);

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

@slider
FinePitch = max(min((_paramFinePitch+100)*0.005,1),0);
CoarsePitch = max(min((_paramCoarsePitch+13)*0.04,1),0.04);
delay = _paramDelay;

(FinePitch != old_FinePitch || CoarsePitch != old_CoarsePitch || delay != old_delay) ? (
// delay
// convert incoming float to int sample count for up to 100ms of delay
_delaySamples = 0.001 * srate * delay|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 - (CoarsePitch * NUM_PITCHES);
fine = ((2 * FinePitch) - 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|0;

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

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

old_FinePitch = FinePitch;
old_CoarsePitch = CoarsePitch;
old_delay = delay;
);

mix = slider6 * 0.01;
output = 10^(slider7/20);

@sample
_paramMix == 0 ? ( //mono
_mixLeftWet = _mixRightWet = 1;
_mixLeftDry = _mixRightDry = 1;
_feedbackPhase = 1;
);
_paramMix == 1 ? ( //stereo
_mixLeftWet = 1;
_mixLeftDry = 1;
_mixRightWet = -1;
_mixRightDry = 1;
_feedbackPhase = 1;
);
_paramMix == 2 ? ( //mono minus
_mixLeftWet = _mixRightWet = 1;
_mixLeftDry = _mixRightDry = 1;
_feedbackPhase = -1;
);
_paramMix == 3 ? ( //stereo minus
_mixLeftWet = 1;
_mixLeftDry = 1;
_mixRightWet = -1;
_mixRightDry = 1;
_feedbackPhase = -1;
);
_paramMix == 4 ? ( //mono wet only
_mixLeftWet = _mixRightWet = 1;
_mixLeftDry = _mixRightDry = 0;
_feedbackPhase = 1;
);
_paramMix == 5 ? ( //stereo wet only
_mixLeftWet = 1;
_mixLeftDry = 0;
_mixRightWet = -1;
_mixRightDry = 0;
_feedbackPhase = 1;
);
_paramMix == 6 ? ( //mono wet only minus
_mixLeftWet = _mixRightWet = 1;
_mixLeftDry = _mixRightDry = 0;
_feedbackPhase = -1;
);
_paramMix == 7 ? ( //stereo wet only minus
_mixLeftWet = 1;
_mixLeftDry = 0;
_mixRightWet = -1;
_mixRightDry = 0;
_feedbackPhase = -1;
);

inval = (spl0 + spl1)*0.5;
inmix = inval + tanh(_paramFeedback * 0.005 * _feedbackPhase * outval);

buf[_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[ep1] * w1 + buf[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[ep1] * w1 + buf[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);
);

_paramMix > 3 ? (
spl0 = (spl0*mix + out0*(1-mix))*output;
spl1 = (spl1*mix + out1*(1-mix))*output;
):(
spl0 = out0*output*0.707;
spl1 = out1*output*0.707;
);

@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');
