desc:MIDI Modal Randomness
//tags: MIDI generator
//author: Schwa

//Convert midi notes into modal randomness

slider1:0<0,11,{1,1.5,2,2.5,3,4,4.5,5,5.5,6,6.5,7>Interval A
slider2:50<0,100,1>Probability A

slider3:4<0,11,{1,1.5,2,2.5,3,4,4.5,5,5.5,6,6.5,7>Interval B
slider4:30<0,100,1>Probability B

slider5:7<0,11,{1,1.5,2,2.5,3,4,4.5,5,5.5,6,6.5,7>Interval C
slider6:40<0,100,1>Probability C

slider7:9<0,11,{1,1.5,2,2.5,3,4,4.5,5,5.5,6,6.5,7>Interval D
slider8:20<0,100,1>Probability D

slider9:50<0,100,1>Speed
slider10:50<0,100,1>Octave Randomness
slider11:50<0,100,1>Timing Randomness
slider12:50<0,100,1>Velocity Randomness

slider13:10<0,600,1>Decay Time (sec)
slider14:4<0,8,1>Number Of Simultaneous Notes

slider15:4<1,8,1>Quantize Targets Per Beat
slider16:0<0,100,1>Quantize Strength

in_pin:none
out_pin:none

@init

// SPB = samples per beat.
// NPB = notes per beat.
// SPN = samples per note.

NOTE_ON = 9;
NOTE_OFF = 8;

MIN_NPB = 0.25;
MAX_NPB = 4.0;
MIN_DECAY_VEL = 2;

// Note buffer offsets.
B_LEN = 0;    // Play this note for this many samples.
B_ITEMS = 1;
buf = 0;
memset(buf, 0, 128*B_ITEMS);

// Input queue offsets.
Q_NOTE = 0;
Q_VEL = 1;  // Input velocity, decays according to speed slider.
Q_POS = 2; // When this note is due to trigger another note.
Q_ITEMS = 3;
queue = 128*B_ITEMS;
memset(queue, 0, 16*Q_ITEMS);

active_notes = 0;
max_active_notes = 0;

@slider

iA = slider1;
pA = 0.01*slider2;
iB = slider3;
pB = 0.01*slider4;
iC = slider5;
pC = 0.01*slider6;
iD = slider7;
pD = 0.01*slider8;

speed = 0.02*slider9;
r_oct = 0.07*slider10;
r_time = 0.01*slider11;
r_vel = 0.02*slider12;

decay = slider13;
max_active_notes = slider14;
q_tgt = slider15;
quant = 0.01*slider16;

pSum = pA+pB+pC+pD;
pSum > 0 ? (
  pA /= pSum;
  pB = pA+pB/pSum;
  pC = pB+pC/pSum;
  pD = pC+pD/pSum;
);

NPB = MIN_NPB+speed*(MAX_NPB-MIN_NPB);
decay_samples = 0.5*decay*srate;


@block

BP0 = beat_position;
SPB = srate * 60.0 / tempo;
SPN = SPB / NPB;

midirecv(mpos, msg1, msg23);
while (
  (msg1/16)|0 == NOTE_ON ? (
    vel = (msg23/256)|0;
    note = msg23 - (vel*256);
  
    pq = active_notes*Q_ITEMS;
    queue[pq+Q_NOTE] = note;
    queue[pq+Q_VEL] = vel;
    queue[pq+Q_POS] = mpos;

    active_notes += 1;
  );
  midirecv(mpos, msg1, msg23);
);

active_notes > max_active_notes ? (
  dq = active_notes - max_active_notes;
  memcpy(queue, queue+dq*Q_ITEMS, max_active_notes*Q_ITEMS);
  active_notes = max_active_notes;
);

active_notes ? (
  qi = 0;
  while (
    dead = 0;
    pq = qi*Q_ITEMS;
    mpos = queue[pq+Q_POS];
    mpos < samplesblock ? (

      note = queue[pq+Q_NOTE];
          
      r = rand(1);
      r < pA ? ( 
        note += iA; 
      ) : (
      r < pB ? (
        note += iB;
      ) : (
      r < pC ? (
        note += iC;
      ) : (
      r < pD ? (
        note += iD;
      ); ); ); );

//      rand(1) < 0.5 ? (
//        note -= 12;
//      );

      oct = (r_oct*(rand(1)-0.5))|0;
      note += oct*12;

      in_vel = queue[pq+Q_VEL];      

      len = (SPN/2*(1.0+r_time*(rand(1)-0.5)))|0;
      pos = len + (SPN*(1.0+r_time*(rand(1)-0.5)))|0;

      qb = (0.5+q_tgt*(BP0+pos/SPB))|0;
      qb = max(qb, 1);
      qpos = (qb-BP0*q_tgt)*(SPB/q_tgt);

      pos = (pos*(1.0-quant)+qpos*quant)|0;
      
      pn = note*B_ITEMS+B_LEN;
      buf[pn] == 0 ? (
        out_vel = (in_vel*(1.0+r_vel*(rand(1)-0.5)))|0;
        buf[pn] = len;
        midisend(mpos, NOTE_ON*16, out_vel*256+note);
      ); // Else skip it.

      in_vel *= 1.0 - pos/decay_samples; //permute?
      in_vel |= 0;

      in_vel < MIN_DECAY_VEL ? (
        dead = 1;
      ) : (
        queue[pq+Q_POS] = pos;
        queue[pq+Q_VEL] = in_vel;        
      );
    ) : (
      queue[pq+Q_POS] = mpos - samplesblock;
    );  

    dead ? (
      memcpy(queue+pq, queue+pq+Q_ITEMS, (active_notes-i)*Q_ITEMS);
      active_notes -= 1;
    ) : (
      qi += 1;
    );

    qi < active_notes;
  );
);

ni = 0;
while (
  pn = ni*B_ITEMS+B_LEN;
  len = buf[pn];
  len > 0 ? (
    len -= samplesblock;
    len < samplesblock ? (
      midisend(len, NOTE_OFF*16, ni);
      len = 0;
    );
    buf[pn] = len;
  );
  ni += 1;
  ni < 128;
);





