desc:midi arp controller

/* quick reference:

    sustains notes in 4 ordered sets.  input notes are added to the currently
      active set.  switch between sets using control notes specified in sliders:
    
      set 1/2/3/4: sends note ons for notes in the set, then sends note offs for 
        any previous set.  if the set is already active, advances to next set.  
        when changing sets, sustained notes are added to the new set.
      stop: sends note offs for any current set.
      next: advances to next set
      delete notes: while sustained, notes played are removed from the active set.
      delete seq: clears the current set.  if there are sustained notes, they
        are retained in the current set.  otherwise, if the set was playing, 
        advances to the next set.
        
    fwiw, this concept needs design work.  a "toggle notes" mode would be nice.
      still big fun though.
    
    ___
    feedback welcome: bang@forum.cockos.com - bang@bangzero.org
*/

slider1:0<0,15,1{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}>channel
slider2:12<0,127,1{0: C0,1: C#0,2: D0,3: Eb0,4: E0,5: F0,6: F#0,7: G0,8: G#0,9: A0,10: Bb0,11: B0,12: C1,13: C#1,14: D1,15: Eb1,16: E1,17: F1,18: F#1,19: G1,20: G#1,21: A1,22: Bb1,23: B1,24: C2,25: C#2,26: D2,27: Eb2,28: E2,29: F2,30: F#2,31: G2,32: G#2,33: A2,34: Bb2,35: B2,36: C3,37: C#3,38: D3,39: Eb3,40: E3,41: F3,42: F#3,43: G3,44: G#3,45: A3,46: Bb3,47: B3,48: C4,49: C#4,50: D4,51: Eb4,52: E4,53: F4,54: F#4,55: G4,56: G#4,57: A4,58: Bb4,59: B4,60: C5,61: C#5,62: D5,63: Eb5,64: E5,65: F5,66: F#5,67: G5,68: G#5,69: A5,70: Bb5,71: B5,72: C6,73: C#6,74: D6,75: Eb6,76: E6,77: F6,78: F#6,79: G6,80: G#6,81: A6,82: Bb6,83: B6,84: C7,85: C#7,86: D7,87: Eb7,88: E7,89: F7,90: F#7,91: G7,92: G#7,93: A7,94: Bb7,95: B7,96: C8,97: C#8,98: D8,99: Eb8,100: E8,101: F8,102: F#8,103: G8,104: G#8,105: A8,106: Bb8,107: B8,108: C9,109: C#9,110: D9,111: Eb9,112: E9,113: F9,114: F#9,115: G9,116: G#9,117: A9,118: Bb9,119: B9,120: C10,121: C#10,122: D10,123: Eb10,124: E10,125: F10,126: F#10,127: G10}>set 1
slider3:15<0,127,1{0: C0,1: C#0,2: D0,3: Eb0,4: E0,5: F0,6: F#0,7: G0,8: G#0,9: A0,10: Bb0,11: B0,12: C1,13: C#1,14: D1,15: Eb1,16: E1,17: F1,18: F#1,19: G1,20: G#1,21: A1,22: Bb1,23: B1,24: C2,25: C#2,26: D2,27: Eb2,28: E2,29: F2,30: F#2,31: G2,32: G#2,33: A2,34: Bb2,35: B2,36: C3,37: C#3,38: D3,39: Eb3,40: E3,41: F3,42: F#3,43: G3,44: G#3,45: A3,46: Bb3,47: B3,48: C4,49: C#4,50: D4,51: Eb4,52: E4,53: F4,54: F#4,55: G4,56: G#4,57: A4,58: Bb4,59: B4,60: C5,61: C#5,62: D5,63: Eb5,64: E5,65: F5,66: F#5,67: G5,68: G#5,69: A5,70: Bb5,71: B5,72: C6,73: C#6,74: D6,75: Eb6,76: E6,77: F6,78: F#6,79: G6,80: G#6,81: A6,82: Bb6,83: B6,84: C7,85: C#7,86: D7,87: Eb7,88: E7,89: F7,90: F#7,91: G7,92: G#7,93: A7,94: Bb7,95: B7,96: C8,97: C#8,98: D8,99: Eb8,100: E8,101: F8,102: F#8,103: G8,104: G#8,105: A8,106: Bb8,107: B8,108: C9,109: C#9,110: D9,111: Eb9,112: E9,113: F9,114: F#9,115: G9,116: G#9,117: A9,118: Bb9,119: B9,120: C10,121: C#10,122: D10,123: Eb10,124: E10,125: F10,126: F#10,127: G10}>set 2
slider4:18<0,127,1{0: C0,1: C#0,2: D0,3: Eb0,4: E0,5: F0,6: F#0,7: G0,8: G#0,9: A0,10: Bb0,11: B0,12: C1,13: C#1,14: D1,15: Eb1,16: E1,17: F1,18: F#1,19: G1,20: G#1,21: A1,22: Bb1,23: B1,24: C2,25: C#2,26: D2,27: Eb2,28: E2,29: F2,30: F#2,31: G2,32: G#2,33: A2,34: Bb2,35: B2,36: C3,37: C#3,38: D3,39: Eb3,40: E3,41: F3,42: F#3,43: G3,44: G#3,45: A3,46: Bb3,47: B3,48: C4,49: C#4,50: D4,51: Eb4,52: E4,53: F4,54: F#4,55: G4,56: G#4,57: A4,58: Bb4,59: B4,60: C5,61: C#5,62: D5,63: Eb5,64: E5,65: F5,66: F#5,67: G5,68: G#5,69: A5,70: Bb5,71: B5,72: C6,73: C#6,74: D6,75: Eb6,76: E6,77: F6,78: F#6,79: G6,80: G#6,81: A6,82: Bb6,83: B6,84: C7,85: C#7,86: D7,87: Eb7,88: E7,89: F7,90: F#7,91: G7,92: G#7,93: A7,94: Bb7,95: B7,96: C8,97: C#8,98: D8,99: Eb8,100: E8,101: F8,102: F#8,103: G8,104: G#8,105: A8,106: Bb8,107: B8,108: C9,109: C#9,110: D9,111: Eb9,112: E9,113: F9,114: F#9,115: G9,116: G#9,117: A9,118: Bb9,119: B9,120: C10,121: C#10,122: D10,123: Eb10,124: E10,125: F10,126: F#10,127: G10}>set 3
slider5:21<0,127,1{0: C0,1: C#0,2: D0,3: Eb0,4: E0,5: F0,6: F#0,7: G0,8: G#0,9: A0,10: Bb0,11: B0,12: C1,13: C#1,14: D1,15: Eb1,16: E1,17: F1,18: F#1,19: G1,20: G#1,21: A1,22: Bb1,23: B1,24: C2,25: C#2,26: D2,27: Eb2,28: E2,29: F2,30: F#2,31: G2,32: G#2,33: A2,34: Bb2,35: B2,36: C3,37: C#3,38: D3,39: Eb3,40: E3,41: F3,42: F#3,43: G3,44: G#3,45: A3,46: Bb3,47: B3,48: C4,49: C#4,50: D4,51: Eb4,52: E4,53: F4,54: F#4,55: G4,56: G#4,57: A4,58: Bb4,59: B4,60: C5,61: C#5,62: D5,63: Eb5,64: E5,65: F5,66: F#5,67: G5,68: G#5,69: A5,70: Bb5,71: B5,72: C6,73: C#6,74: D6,75: Eb6,76: E6,77: F6,78: F#6,79: G6,80: G#6,81: A6,82: Bb6,83: B6,84: C7,85: C#7,86: D7,87: Eb7,88: E7,89: F7,90: F#7,91: G7,92: G#7,93: A7,94: Bb7,95: B7,96: C8,97: C#8,98: D8,99: Eb8,100: E8,101: F8,102: F#8,103: G8,104: G#8,105: A8,106: Bb8,107: B8,108: C9,109: C#9,110: D9,111: Eb9,112: E9,113: F9,114: F#9,115: G9,116: G#9,117: A9,118: Bb9,119: B9,120: C10,121: C#10,122: D10,123: Eb10,124: E10,125: F10,126: F#10,127: G10}>set 4
slider6:14<0,127,1{0: C0,1: C#0,2: D0,3: Eb0,4: E0,5: F0,6: F#0,7: G0,8: G#0,9: A0,10: Bb0,11: B0,12: C1,13: C#1,14: D1,15: Eb1,16: E1,17: F1,18: F#1,19: G1,20: G#1,21: A1,22: Bb1,23: B1,24: C2,25: C#2,26: D2,27: Eb2,28: E2,29: F2,30: F#2,31: G2,32: G#2,33: A2,34: Bb2,35: B2,36: C3,37: C#3,38: D3,39: Eb3,40: E3,41: F3,42: F#3,43: G3,44: G#3,45: A3,46: Bb3,47: B3,48: C4,49: C#4,50: D4,51: Eb4,52: E4,53: F4,54: F#4,55: G4,56: G#4,57: A4,58: Bb4,59: B4,60: C5,61: C#5,62: D5,63: Eb5,64: E5,65: F5,66: F#5,67: G5,68: G#5,69: A5,70: Bb5,71: B5,72: C6,73: C#6,74: D6,75: Eb6,76: E6,77: F6,78: F#6,79: G6,80: G#6,81: A6,82: Bb6,83: B6,84: C7,85: C#7,86: D7,87: Eb7,88: E7,89: F7,90: F#7,91: G7,92: G#7,93: A7,94: Bb7,95: B7,96: C8,97: C#8,98: D8,99: Eb8,100: E8,101: F8,102: F#8,103: G8,104: G#8,105: A8,106: Bb8,107: B8,108: C9,109: C#9,110: D9,111: Eb9,112: E9,113: F9,114: F#9,115: G9,116: G#9,117: A9,118: Bb9,119: B9,120: C10,121: C#10,122: D10,123: Eb10,124: E10,125: F10,126: F#10,127: G10}>stop
slider7:13<0,127,1{0: C0,1: C#0,2: D0,3: Eb0,4: E0,5: F0,6: F#0,7: G0,8: G#0,9: A0,10: Bb0,11: B0,12: C1,13: C#1,14: D1,15: Eb1,16: E1,17: F1,18: F#1,19: G1,20: G#1,21: A1,22: Bb1,23: B1,24: C2,25: C#2,26: D2,27: Eb2,28: E2,29: F2,30: F#2,31: G2,32: G#2,33: A2,34: Bb2,35: B2,36: C3,37: C#3,38: D3,39: Eb3,40: E3,41: F3,42: F#3,43: G3,44: G#3,45: A3,46: Bb3,47: B3,48: C4,49: C#4,50: D4,51: Eb4,52: E4,53: F4,54: F#4,55: G4,56: G#4,57: A4,58: Bb4,59: B4,60: C5,61: C#5,62: D5,63: Eb5,64: E5,65: F5,66: F#5,67: G5,68: G#5,69: A5,70: Bb5,71: B5,72: C6,73: C#6,74: D6,75: Eb6,76: E6,77: F6,78: F#6,79: G6,80: G#6,81: A6,82: Bb6,83: B6,84: C7,85: C#7,86: D7,87: Eb7,88: E7,89: F7,90: F#7,91: G7,92: G#7,93: A7,94: Bb7,95: B7,96: C8,97: C#8,98: D8,99: Eb8,100: E8,101: F8,102: F#8,103: G8,104: G#8,105: A8,106: Bb8,107: B8,108: C9,109: C#9,110: D9,111: Eb9,112: E9,113: F9,114: F#9,115: G9,116: G#9,117: A9,118: Bb9,119: B9,120: C10,121: C#10,122: D10,123: Eb10,124: E10,125: F10,126: F#10,127: G10}>next
slider8:20<0,127,1{0: C0,1: C#0,2: D0,3: Eb0,4: E0,5: F0,6: F#0,7: G0,8: G#0,9: A0,10: Bb0,11: B0,12: C1,13: C#1,14: D1,15: Eb1,16: E1,17: F1,18: F#1,19: G1,20: G#1,21: A1,22: Bb1,23: B1,24: C2,25: C#2,26: D2,27: Eb2,28: E2,29: F2,30: F#2,31: G2,32: G#2,33: A2,34: Bb2,35: B2,36: C3,37: C#3,38: D3,39: Eb3,40: E3,41: F3,42: F#3,43: G3,44: G#3,45: A3,46: Bb3,47: B3,48: C4,49: C#4,50: D4,51: Eb4,52: E4,53: F4,54: F#4,55: G4,56: G#4,57: A4,58: Bb4,59: B4,60: C5,61: C#5,62: D5,63: Eb5,64: E5,65: F5,66: F#5,67: G5,68: G#5,69: A5,70: Bb5,71: B5,72: C6,73: C#6,74: D6,75: Eb6,76: E6,77: F6,78: F#6,79: G6,80: G#6,81: A6,82: Bb6,83: B6,84: C7,85: C#7,86: D7,87: Eb7,88: E7,89: F7,90: F#7,91: G7,92: G#7,93: A7,94: Bb7,95: B7,96: C8,97: C#8,98: D8,99: Eb8,100: E8,101: F8,102: F#8,103: G8,104: G#8,105: A8,106: Bb8,107: B8,108: C9,109: C#9,110: D9,111: Eb9,112: E9,113: F9,114: F#9,115: G9,116: G#9,117: A9,118: Bb9,119: B9,120: C10,121: C#10,122: D10,123: Eb10,124: E10,125: F10,126: F#10,127: G10}>delete notes
slider9:23<0,127,1{0: C0,1: C#0,2: D0,3: Eb0,4: E0,5: F0,6: F#0,7: G0,8: G#0,9: A0,10: Bb0,11: B0,12: C1,13: C#1,14: D1,15: Eb1,16: E1,17: F1,18: F#1,19: G1,20: G#1,21: A1,22: Bb1,23: B1,24: C2,25: C#2,26: D2,27: Eb2,28: E2,29: F2,30: F#2,31: G2,32: G#2,33: A2,34: Bb2,35: B2,36: C3,37: C#3,38: D3,39: Eb3,40: E3,41: F3,42: F#3,43: G3,44: G#3,45: A3,46: Bb3,47: B3,48: C4,49: C#4,50: D4,51: Eb4,52: E4,53: F4,54: F#4,55: G4,56: G#4,57: A4,58: Bb4,59: B4,60: C5,61: C#5,62: D5,63: Eb5,64: E5,65: F5,66: F#5,67: G5,68: G#5,69: A5,70: Bb5,71: B5,72: C6,73: C#6,74: D6,75: Eb6,76: E6,77: F6,78: F#6,79: G6,80: G#6,81: A6,82: Bb6,83: B6,84: C7,85: C#7,86: D7,87: Eb7,88: E7,89: F7,90: F#7,91: G7,92: G#7,93: A7,94: Bb7,95: B7,96: C8,97: C#8,98: D8,99: Eb8,100: E8,101: F8,102: F#8,103: G8,104: G#8,105: A8,106: Bb8,107: B8,108: C9,109: C#9,110: D9,111: Eb9,112: E9,113: F9,114: F#9,115: G9,116: G#9,117: A9,118: Bb9,119: B9,120: C10,121: C#10,122: D10,123: Eb10,124: E10,125: F10,126: F#10,127: G10}>delete seq
slider10:0<0,4,1{-,1,2,3,4}>current seq (ro)

@init
ext_noinit = 1;
seq = 0;
len = 0;
suscnt = 0;
maxlen = 200;
len1 = 0;
len2 = 0;
len3 = 0;
len4 = 0;
delnotes = 0;
// arrays
seq1 = 5;
seq2 = seq1 + maxlen;
seq3 = seq2 + maxlen;
seq4 = seq3 + maxlen;
serialmem = seq4 + maxlen;
sus = serialmem; // sustained note counts

@serialize
file_var(0,len1);
file_var(0,len2);
file_var(0,len3);
file_var(0,len4);
file_mem(0,0,serialmem);

@slider
chan = slider1;
seq1note = slider2;
seq2note = slider3;
seq3note = slider4;
seq4note = slider5;
stopnote = slider6;
nextnote = slider7;
delnotesnote = slider8;
delseqnote = slider9;
//cur seq = slider9 ! set below

@block

while (
  midirecv(ts,msg,msg23) ?( 
    // our channel?
    msg & $xf == chan ?(
  
      thru = 1;
      
      // sustain?
      (msg&$xf0) == $xb0 && (msg23&$xff) == 64 ?(
        thru = 0;
        // fake trig note for next nonempty seq
        msg23 & $xff00 ?( // on
          seq == seq1 ? ( 
              len2 ? trig = seq2note : len3 ? trig = seq3note : len4 ? trig = seq4note : trig = seq2note; )
          : seq == seq2 ? ( 
              len3 ? trig = seq3note : len4 ? trig = seq4note : len1 ? trig = seq1note : trig = seq3note; )
          : seq == seq3 ? ( 
              len4 ? trig = seq4note : len1 ? trig = seq1note : len2 ? trig = seq2note : trig = seq4note; )
          : seq == seq4 ? ( 
              len1 ? trig = seq1note : len2 ? trig = seq2note : len3 ? trig = seq3note : trig = seq1note; );
          msg = $x90 + chan; msg23 = trig + $x100;
        );
      );
      
      // note on or off
      (msg&$xe0) == $x80 ?( 
      
        vel = (msg23 / 256) & $xff;
        note = msg23 & $xff;
        
        // note on
        msg&$x10 && vel ?( 
        
          stoplen = newseq = delseq = 0;
          unnorm = 1;
          
          // fake trig note for next nonempty seq
          note == nextnote 
            || (note == seq1note && seq == seq1)
            || (note == seq2note && seq == seq2)
            || (note == seq3note && seq == seq3)
            || (note == seq4note && seq == seq4)
          ?(
            seq == seq1 ? ( 
                len2 ? trig = seq2note : len3 ? trig = seq3note : len4 ? trig = seq4note : trig = seq2note; )
            : seq == seq2 ? ( 
                len3 ? trig = seq3note : len4 ? trig = seq4note : len1 ? trig = seq1note : trig = seq3note; )
            : seq == seq3 ? ( 
                len4 ? trig = seq4note : len1 ? trig = seq1note : len2 ? trig = seq2note : trig = seq4note; )
            : seq == seq4 ? ( 
                len1 ? trig = seq1note : len2 ? trig = seq2note : len3 ? trig = seq3note : trig = seq1note; );
            note = trig;
          );
  
          // recognize trig notes
          newseq = 1; stoplen = len; stopseq = seq; //defaults
          note == stopnote ?( lastseq = seq; lastlen = len; seq = len = newseq = 0; )
          : note == seq1note ?( seq != seq1 ?( seq = seq1; len = len1; ); )
          : note == seq2note ?( seq != seq2 ?( seq = seq2; len = len2; ); )
          : note == seq3note ?( seq != seq3 ?( seq = seq3; len = len3; ); )
          : note == seq4note ?( seq != seq4 ?( seq = seq4; len = len4; ); )
          : note == delseqnote ?( !seq ?( seq = lastseq; len = lastlen; newseq = 0; ); seq ? delseq = 1; )
          : note == delnotesnote ?( delnotes = 1; )
          
          // normal note on
          :(
            unnorm = newseq = stoplen = 0;
            delnotes ?(
              seq && len ?(
                copy = 0;
                ii = 0;
                while (
                  !copy && seq[ii] == note ?( 
                    midisend(ts,$x80+chan,note);
                    len -= 2;
                    seq == seq1 ? len1 = len : seq == seq2 ? len2 = len
                      : seq == seq3 ? len3 = len : seq == seq4 ? len4 = len;
                    copy = 1;
                  );
                  copy ?(  
                    seq[ii] = seq[ii+2]; 
                    seq[ii+1] = seq[ii+3];
                  );
                  (ii += 2) < len;
                );
              );
              // bump sus cnt to inhibit eventual noteoff
              sus[note] += 1;
              suscnt += 1;
            ):(
              midisend(ts,msg,msg23);
              // add to cur seq
              seq && len < maxlen ?(
                seq[len] = note;
                seq[len+1] = vel;
                len += 2;
                seq == seq1 ? len1 = len : seq == seq2 ? len2 = len
                  : seq == seq3 ? len3 = len : seq == seq4 ? len4 = len;
                // bump sus cnt
                sus[note] += 1;
                suscnt += 1;
              );
            );
          );
          
          unnorm ? (
            
            delseq ?(
              // currently sus'd notes?
              suscnt && len ?(
                stoplen = newseq = 0;
                // note offs for unsus'd
                ii = len - suscnt * 2;
                jj = 0;
                while (
                  midisend(ts,$x80+chan,seq[jj]);
                  (jj += 2) < ii;
                );
                // move sus'd notes to beg of seq
                jj = 0;
                while (
                  seq[jj] = seq[ii];
                  seq[jj+1] = seq[ii+1];
                  jj += 2;
                  (ii += 2) < len;
                );
                len = suscnt * 2;
                seq == seq1 ? len1 = len : seq == seq2 ? len2 = len
                  : seq == seq3 ? len3 = len : seq == seq4 ? len4 = len;
                  
              // nothing sus'd - delete seq
              ):(
                // zero seq len && select next non-empty seq to play
                seq == seq1 ?(
                  len1 = 0; 
                  newseq ? len2 ? seq = seq2 : len3 ? seq = seq3 : len4 ? seq = seq4 : newseq = 0;
                ): seq == seq2 ?(
                  len2 = 0; 
                  newseq ? len3 ? seq = seq3 : len4 ? seq = seq4 : len1 ? seq = seq1 : newseq = 0;
                ): seq == seq3 ?(
                  len3 = 0; 
                  newseq ? len4 ? seq = seq4 : len1 ? seq = seq1 : len2 ? seq = seq2 : newseq = 0;
                ): seq == seq4 ?(
                  len4 = 0; 
                  newseq ? len1 ? seq = seq1 : len2 ? seq = seq2 : len3 ? seq = seq3 : newseq = 0; 
                );
                newseq ?(
                  seq == seq1 ? len = len1 : seq == seq2 ? len = len2
                  : seq == seq3 ? len = len3 : seq == seq4 ? len = len4 )
                : len = 0;
              );
            );
                
            // send note ons for new seq
            newseq ?(
              len ?(
                ii = 0;
                while (
                  midisend(ts,$x90+chan,seq[ii]+seq[ii+1]*256);
                  (ii += 2) < len;
                );
              );
              // steal sus'd notes from current seq
              suscnt && stoplen ?(
                ii = stoplen - suscnt * 2;
                jj = len;
                while (
                  seq[jj] = stopseq[ii];
                  seq[jj+1] = stopseq[ii+1];
                  jj += 2;
                  (ii += 2) < stoplen;
                );
                len = jj;
                seq == seq1 ? len1 = len : seq == seq2 ? len2 = len
                    : seq == seq3 ? len3 = len : seq == seq4 ? len4 = len;
                stoplen -= suscnt * 2;
                stopseq == seq1 ? len1 = stoplen : stopseq == seq2 ? len2 = stoplen
                    : stopseq == seq3 ? len3 = stoplen : stopseq == seq4 ? len4 = stoplen;
              );
            );
    
            // note offs for cancelled seq
            stoplen ?(
              ii = 0;
              while (
                midisend(ts+1,$x80+chan,stopseq[ii]);
                (ii += 2) < stoplen;
              );
            );
            
            // update cur seq slider
            seq == seq1 ? slider10 = 1 : seq == seq2 ? slider10 = 2
                : seq == seq3 ? slider10 = 3 : seq == seq4 ? slider10 = 4
                : slider10 = 0;
            sliderchange(slider10);
            
          ); // endif unnorm
            
        // note off
        ):(
          note == delnotesnote ?( delnotes = 0; )
          : !( note == stopnote || note == delseqnote || note == nextnote
              || note == seq1note || note == seq2note || note == seq3note || note == seq4note 
            ) 
          ?(        
            // dec sus cnt if nec
            sus[note] ?( 
              suscnt -= 1; 
              sus[note] -= 1;
            // otherwise send noteoff if not trig note
            ):( 
              midisend(ts,msg,msg23);
            );
          );
        );
        
      // not note off/on
      ):(
        thru ? midisend(ts,msg,msg23);
      );
      
    // not our channel
    ):(
      midisend(ts,msg,msg23); 
    ); 
    1; // always loop if received msg
  );
  
);
