// License: GPL - http://www.gnu.org/licenses/gpl.html
// avocado (ducking glitch generator)
// by daniel arena (dan@remaincalm.org)
// http://remaincalm.org
// 2008/09/16 - first version! contact me with bugs etc
// 2008/09/20 - updated: tempo sync, handle slider and buffer changes more gracefully

desc: avocado (ducking glitch generator)

slider1:50<0,4000,1>buffer length (ms)
slider2:90<0,100,1>mix (%)
slider3:8<1,16,1>buffers 
slider4:70<0,99,1>repeat prob (%)
slider5:5<0,100,1>pitch mod prob (%)
slider6:10<0,99,1>reverse prob (%) 
slider7:18<0,99,1>fadeout prob (%) 
slider8:8<0,99,1>threshhold (%)
slider9:15<0,99,1>glitch attack (%)
slider10:0<0,4,1{off,major,minor,fifths,octaves}>arpeg mode
slider11:0<0,64,1>tempo sync (# 64th notes, 0 to disable)
//slider12:0<0,100,1>modulate buffer length?

@init
pi = 3.141592653589793;
fade_samples = 48; // controls fade in/out on buffer playback
play_rate = 1;
max_bufsiz = ceil(4000 * srate / 1000);
max_buffers = 16;
r_offset = max_bufsiz * max_buffers + 1;
notedata_offset = r_offset * 2;
sync_counter = 0;
is_recording = 0;
playback_buffer = 0;
playback_csr = 0;
record_buffer = 0;
record_csr = 0;

@block
(slider11 > 0 && tempo > 0) ?
(
  (slider1 | 0 != (1000 * slider11 * 60/16/tempo) | 0) ?
    (slider1 = 1000 * slider11 * 60/16/tempo;);
);
//slider12 = can_start_rec_or_play;

@slider
bufsiz = slider1 * srate / 1000;
 
// arpeg data major
(slider10 == 1) ?
(
    // 1.18920 -- minor
    max_arpeg = 4;
    note[notedata_offset + 0] = 1;
    note[notedata_offset + 1] = 1.25993;
    note[notedata_offset + 2] = 1.49830;
    note[notedata_offset + 3] = 2;
);
// arpeg data minor
(slider10 == 2) ?
(
    // 1.18920 -- minor
    max_arpeg = 4;
    note[notedata_offset + 0] = 1;
    note[notedata_offset + 1] = 1.18920;
    note[notedata_offset + 2] = 1.49830;
    note[notedata_offset + 3] = 2;
);
// arpeg data fifths
(slider10 == 3) ?
(
    // 1.33485 -- ?
    max_arpeg = 3;
    note[notedata_offset + 0] = 1;
    note[notedata_offset + 1] = 1.49830;
    note[notedata_offset + 2] = 2;
);
// arpeg data octaves
(slider10 == 4) ?
(
    // 1.33485 -- ?
    max_arpeg = 3;
    note[notedata_offset + 0] = 1;
    note[notedata_offset + 1] = 2;
    note[notedata_offset + 2] = 3;
);


@sample
/*
record:
  if rnd() > ?, start recording else stop recording.
  if recording, add to buffer. if buffer full, stop recording.
*/
// if we're beat syncing, only record on start of beat etc
can_start_rec_or_play = (old_tempo == tempo && old_slider1 == slider1) ? can_start_rec_or_play : false;
can_start_rec_or_play = can_start_rec_or_play || (slider11 == 0);
can_start_rec_or_play = can_start_rec_or_play || (((beat_position * 256) | 0) == ((beat_position | 0) * 256));
old_tempo = tempo;
old_slider1 = slider1;

(is_recording) ?
(
  buffer[max_bufsiz*record_buffer + record_csr] = spl0;
  buffer[r_offset + max_bufsiz*record_buffer + record_csr] = spl1;
  record_csr += 1;

  record_csr > bufsiz ? 
  (
    // TODO: measure volume level of that block
    is_recording = false;
    
    // resync occasionally (every 16 sec)
    sync_counter += 1;
    (slider11 > 0 && sync_counter > srate * 16 / bufsiz) ? 
    (
      sync_counter = 0;
      can_start_rec_or_play = 0;
    );
    
  );

):
(
  can_start_rec_or_play ?
  (
    // start recording
    record_buffer = floor(rand(slider3));
    record_csr = 0;
    is_recording = 1;
  );
);


/*
playback:
  if buffer ended, choose either same buffer or a new buffer  
  play selected buffer
*/
playback_csr = (can_start_rec_or_play) ? playback_csr : 0;
floor(playback_csr) > bufsiz ?
(
  playback_csr = 0;

  // TODO: try and choose a block that matches last input volume level?

  rand(100) > slider4 ?
  (
    playback_buffer = floor(rand(slider3));
  );

  // change rate  
  (slider10 > 0) ? /*  c64 style! */
  (
    play_rate = note[notedata_offset + arpeg_idx];
    arpeg_idx = (arpeg_idx + 1) % max_arpeg;

    //play_rate = (play_rate == 1) ? 1.25993 : (play_rate == 1.25993) ? 2 : 1;    
  ) : (
    // normal!
    play_rate = 1;
    (rand(100)> 110 - slider5) ?
    (
      play_rate = 2;
    );
  ) ;  

  // mutilation options
  // reverse block?
  ( rand(100)> 105 - slider6) ?
  (
    i=0;
    loop
    (ceil(bufsiz/2 - 2),
      start_pos = max_bufsiz*playback_buffer + i;
      end_pos = max_bufsiz*playback_buffer + bufsiz - i - 1;
      swapL = buffer[start_pos];
      swapR = buffer[r_offset + start_pos];
      buffer[start_pos] = buffer[end_pos];
      buffer[r_offset + start_pos] = buffer[r_offset + end_pos];
      buffer[end_pos] = swapL;
      buffer[r_offset + end_pos] = swapR;
      i += 1;
    );
  );

  // quieten block?
  ( rand(100)> 105 - slider7) ?
  (
    i=0;
    loop
    (ceil(bufsiz),
      buffer[max_bufsiz*playback_buffer + i] *= 0.85;
      buffer[r_offset + max_bufsiz*playback_buffer + i] *= 0.85;
      i += 1;
    );
  );
);

// fade edges
mult = 1;

(floor(playback_csr) < fade_samples) ?
(
  mult = floor(playback_csr)/fade_samples;
) :
(
  (floor(playback_csr) > bufsiz - fade_samples) ?
  (
    mult = (bufsiz - floor(playback_csr) - 1)/fade_samples;
  );
); 

// calculate raw output
tmpL = mult * buffer[max_bufsiz*playback_buffer + floor(playback_csr)];
tmpR = mult * buffer[r_offset + max_bufsiz*playback_buffer + floor(playback_csr)];

// move cursor
playback_csr += play_rate;


// TODO: filter?

// calculate threshold - moving slightly to correct for input level
(max(abs(spl1),abs(spl0)) > thresh) ?
(
  thresh = thresh * 0.9 + 0.1 * max(abs(spl1),abs(spl0));
) :
(
  thresh = thresh * 0.98 + 0.02 * max(abs(spl1),abs(spl0));
);
(thresh > max(slider8/100, max_thresh * slider8/65)) ?
(
  target_gain = 1;
) : (
  target_gain = 1 - (slider2/100);
);
max_thresh = (thresh > max_thresh) ? max(max_thresh, max_thresh * 0.1 + thresh * 0.9) : max_thresh * 0.99998;

// output mix
(target_gain > gain) ?
(
  gain = gain * 0.95 + 0.05 * target_gain;
) : 
(
  gain = gain * (1 - slider9 / 100000) + slider9 / 100000 * target_gain;
);
last_gain = gain;

// output
(can_start_rec_or_play) ?
(
  spl0 = spl0 * gain + tmpL*(1-gain);
  spl1 = spl1 * gain + tmpR*(1-gain);
) :
(
  spl0 = spl0;// * gain + tmpL*(1-gain);
  spl1 = spl1; // * gain + tmpR*(1-gain);
);



