// License: GPL - http://www.gnu.org/licenses/gpl.html
// floaty (modulated delay)
// by daniel arena (dan@remaincalm.org)
// http://remaincalm.org
// 2008/09/16 - first version! contact me with bugs etc
// 2008/09/20 - updated: less glitching on overlap, tempo sync (backward compatible)
// 2008/10/04 - updated: L/R offset
// 2009/10/08 - updated: fixed bug where L/R offset active with playrate != 1, tweaked clamping behaviour (default = old behaviour)
// 2009/10/09 - updated: fixed default clamp value

desc: floaty (modulated delay)

slider1:250<0,4000,1>delay (ms)
slider2:40<0,100,1>mix(%)
slider3:30<0,80,1>feedback(%)
slider4:0.1<0,10,0.001>warp rate (Hz)
slider5:0.5<0,4,0.1>warp amount (%)
slider6:80<0,100,1>filter cutoff (%)
slider7:80<0,150,1>filter resonance (%)
slider8:1<-2,2,0.5>playback rate
slider9:0<0,64,1>tempo sync (# 32nd notes, 0 to disable)
slider10:98<70,100,1>L/R offset (%)
slider11:6<-12,12,0.1>Pre-filter clamp (dB)

@init
pi = 3.141592653589793;
old_delaysiz = -1; 
reset = 1;
clamp = 2; // ugly limiter (pre-filter)
target_clamp = clamp;
smooth_overlap = 128; // smooth out if rec/play csr overlap

@block
(slider9 > 0 && tempo > 0) ?
(
  slider1 = 1000 * slider9 * 60/8/tempo;
);

@slider

delaysiz = (slider1 * srate / 1000)|0;
bufsiz = delaysiz*4; //max(delaysiz, srate);
(delaysiz != old_delaysiz || slider8 != old_slider8 || slider4 != old_slider4 || slider10 != old_slider10 ) ?
(
  play_csr = 0;
  rec_csrL = delaysiz-2 ;
  rec_csrR = (slider10 * rec_csrL / 100) | 0;
  t = 0;
  reset = 1;
  old_delaysiz = delaysiz; 
  old_slider8 = slider8;
  old_slider4 = slider4;
  old_slider10 = slider10;
);

rate = 2*pi*slider4/srate;

// RC filter params (hi/lo)
LPF_c = 0.5^(5-(slider6/25));
LPF_r = 0.5^((slider7/40)-0.6);
HPF_c = 0.5^4.8;
HPF_r = 0.5^(3-(slider7/40));

// update clamp value
target_clamp = 2 ^ (slider11/6);


@sample

// move play head
lplay_csr = play_csr;
play_csr += (reset>0) ? slider8 : (slider8 + (slider5/100) * sin(t)); 
t+= (reset>0) ? 0 : rate;
reset = (play_csr >= bufsiz) ? 0 : reset; // allow one full record cycle before modulation
play_csr += (play_csr >= bufsiz) ? -bufsiz: 0;
play_csr += (play_csr < 0) ? bufsiz: 0;

play_csr_0 = 0|play_csr;
play_csr_1 = (play_csr_0+1 == bufsiz) ? 0 : play_csr_0+1;

// get from play head
frac0= play_csr - play_csr_0;
tmpL =  (buf[play_csr_0] * (1-frac0) + buf[play_csr_1] * (frac0));
tmpR =  (buf[play_csr_0+bufsiz] * (1-frac0) + buf[play_csr_1+bufsiz] * (frac0));

// clean up if play/rec overlapping
overlap_distL = abs((play_csr % bufsiz) - (rec_csrL % bufsiz));
overlap_multL = (overlap_distL >= smooth_overlap) ? 1 : overlap_distL/smooth_overlap;

overlap_distR = abs((play_csr % bufsiz) - (rec_csrR % bufsiz));
overlap_multR = (overlap_distR >= smooth_overlap) ? 1 : overlap_distR/smooth_overlap;

tmpL *= overlap_multL;
tmpR *= overlap_multR;

// clamp (before filter!)
// updated
(clamp != target_clamp) ? (clamp = 0.8 * clamp + 0.2 * target_clamp) ;
(abs(tmpL) > clamp) ? ( tmpL = (tmpL > 0) ? clamp : -clamp );
(abs(tmpR) > clamp) ? ( tmpR = (tmpR > 0) ? clamp : -clamp );


// run filter
(slider6 > 0 && slider7 > 0) ?
(
  // new lpf
  v0L = (1 - LPF_r*LPF_c)*v0L - LPF_c*v1L + LPF_c*tmpL;
  v1L = (1 - LPF_r*LPF_c)*v1L + LPF_c*v0L;
  tmpL = v1L;

  // new hpf 
  hv0L = (1 - HPF_r*HPF_c)*hv0L - HPF_c*hv1L + HPF_c*tmpL;
  hv1L = (1 - HPF_r*HPF_c)*hv1L + HPF_c*hv0L;
  tmpL -= hv1L;

  // new lpf
  v0R = (1 - LPF_r*LPF_c)*v0R - LPF_c*v1R + LPF_c*tmpR;
  v1R = (1 - LPF_r*LPF_c)*v1R + LPF_c*v0R;
  tmpR = v1R;

  // new hpf 
  hv0R = (1 - HPF_r*HPF_c)*hv0R - HPF_c*hv1R + HPF_c*tmpR;
  hv1R = (1 - HPF_r*HPF_c)*hv1R + HPF_c*hv0R;
  tmpR -= hv1R;
);

// store and feedback
buf[rec_csrL]= tmpL * slider3/100 + spl0;
buf[rec_csrR+bufsiz]= tmpR * slider3/100 + spl1;

// move rec head
rec_csrL += 1;
rec_csrL += (rec_csrL >= bufsiz) ? -bufsiz : 0;
rec_csrL += (rec_csrL < 0) ? bufsiz : 0;

rec_csrR += 1;
rec_csrR += (rec_csrR >= bufsiz) ? -bufsiz : 0;
rec_csrR += (rec_csrR < 0) ? bufsiz : 0;

// output
spl0 = tmpL*(slider2/100) + spl0*(1-(slider2/100));
spl1 = tmpR*(slider2/100) + spl1*(1-(slider2/100));
