desc:Midi Monoizer
/* Midi Monoizer (c)2019 by Andrew Shakinovsky andrew@afrittemple.com
   I welcome comments or suggestions.
Description:
   Insert this before a VSTi in the chain. It will prevent multiple midi notes from
   sounding at the same time. When a note is playing and another note is received,
   a note-off is sent for the first note before(*) the new note is sent. This is akin
   to typical monosynths when you are holding a key and then press and release another 
   key: the first key plays, then the second, and when you release the second, the first
   key will play again. A lot of VSTi's behave in this way, but there are some that do
   not, and that is where this becomes helpful.
   
   (*) will be sent after the note on depending on the legato slider. If the legato slider
   is no zero, the note off will be delayed by that many samples to allow the synth to 
   treat it as legato playing
*/
slider1:0<0,256>Legato Delay Samples
@init
stackptr=-1;
// array offsetting (there is only one local mem space)
stacknote=0;
stackvel=1024;
stackchan=2048;
nno_Off=0;
nno_Chan=0;
nno_Note=0;
@block

// play note off, applying legator delay
function note_off(pos, chan, note) (
  // make sure it fits in this block given the delay
  pos+slider1 < samplesblock ? (
    midisend(pos+slider1,0x80 | chan, note);
  ) : (
    // doesn't belong in this block, send it in the next block
    nno_Off = 1 + ((pos+slider1) - samplesblock);
    nno_Chan=chan;
    nno_Note=note;
  );
);

// do we have a note off to send?
nno_Off ? (
  midisend(nno_Off,0x80 | nno_Chan, nno_Note);
  nno_Off=0; 
);

while (  
  // if we receive a message
  midirecv(ofs, msg1, msg23) ? (
  
    status = msg1 & 0xF0; // hi 4 bits
    chan = msg1 & 0x0F; // low 4 bits
    note = msg23 & 0xFF; //low order byte is note
    velocity = msg23 >> 8; // high order byte is velocity

    // if its a Note On
    status == 0x90 && velocity ? (
      // if we have items on stack
      stackptr > -1 ? (
        note_off(ofs, stackchan[stackptr], stacknote[stackptr]);
      );
      
      // push curent new note to stack
      stackptr+=1;
      stacknote[stackptr]=note;
      stackvel[stackptr]=velocity;
      stackchan[stackptr]=chan;   
    );

    // if its a Note Off
    status == 0x80 || (status == 0x90 && !velocity) ? (
      
      // if the note doesn't match top of stack, remove it below in the stack
      // shifting everything down (we don't care about it just get rid of it)
      note != stacknote[stackptr] ? (
        i=0;
        rp=0;
        while(i <= stackptr) (
          stacknote[i]==note ? ( // if it matches the note, move our read position
            rp += 1;  
          );
          // copy source to target
          stacknote[i] = stacknote[rp];
          stackvel[i] = stackvel[rp];
          stackchan[i] = stackchan[rp]; 
          i += 1;
          rp += 1;
        );
        // set new top of stack
        stackptr-= (rp-i);
        
      ) : ( // otherwise, note matches top of stack ...
        stackptr > -1 ? (
          // pop stack 
          stackptr-=1;
          // send note on for tos (if any)
          midisend(ofs,0x90 | stackchan[stackptr], 
            (stackvel[stackptr] << 8) | stacknote[stackptr]);        
        );
      ); 
    ):

    // All Notes Off
    status == 0xB0 && cc == 123 & n ? (
      stackptr=-1; // discard all
    );
   
    // send original incoming message, but if it's a note off, apply delay
    msg1 ? (
      status == 0x80 || (status == 0x90 && !velocity) ? (
        note_off(ofs, chan, note); 
      ):(
        midisend(ofs, msg1, msg23);
      );
    );
  ); // if we recv a msg (otherwise the while will exit)
); // while