/*******************************************************************************
*  Portions Copyright 2007 - 2013, Philip S. Considine                         *
*  Used under the terms of the GNU General Public License as published by      *
*  the Free Software Foundation, latest version.                               *
*                                                                              *
*******************************************************************************/

desc:MIDI Scale filter-harmonizer v0.32

in_pin:none
out_pin:none

slider3:0<0,11,1{C,C#,D,D#,E,F,F#,G,G#,A,A#,B}>Key
slider4:0<0,2,1{Major,Harmonic Minor,Melodic Minor}>Base Scale
slider5:0<0,6,1{1st,2nd,3rd,4th,5th,6th,7th}>Mode
slider8:0<0,6,1{Filter Only,Generate Triads,Generate 7ths,Generate 9ths,Generate 11ths,Generate 13ths}>Function

slider20:0<0,16,1>Input MIDI Channel (0=Any)

slider31:0<0,127,1>Harmony Range Low Note
slider32:127<0,127,1>Harmony Range High Note
slider33:0<0,20,1>Harmony Velocity Variability (%)

slider37:0<-2,2,1>Transpose Root (octaves)
slider38:1<1,16,1>Root Output MIDI Channel
slider40:0<-2,2,1>Transpose 3rd (octaves)
slider41:1<1,16,1>3rd Output MIDI Channel
slider43:0<-2,2,1>Transpose 5th (octaves)
slider44:1<1,16,1>5th Output MIDI Channel
slider46:0<-2,2,1>Transpose 7th (octaves)
slider47:1<1,16,1>7th Output MIDI Channel
slider49:0<-2,2,1>Transpose 9th (octaves)
slider50:1<1,16,1>9th Output MIDI Channel
slider52:0<-2,2,1>Transpose 11th (octaves)
slider53:1<1,16,1>11th Output MIDI Channel
slider55:0<-2,2,1>Transpose 13th (octaves)
slider56:1<1,16,1>13th Output MIDI Channel

@init     ///////////////////////////////////

statNoteOff = $x80;
statNoteOn = $x90;

baseScale = 1024;
baseScale[0] = $xAB5;  //      Major scale //
baseScale[1] = $x9AD;  //   Harmonic Minor // 
baseScale[2] = $xAAD;  //    Melodic Minor //

channelOut = 2048;
buff_size = 8;
  memset(channelOut, 0, buff_size);
transpose = channelOut + buff_size;
  memset(transpose, 0, buff_size);
  
@slider   ///////////////////////////////////

key = slider3;
scale = baseScale[slider4];
isMode = sign(slider5);
mode = slider5;
isHarmony = sign(slider8);
harmony = slider8 + 1;
inChannel = min(max(slider20 - 1 | 0, -1), 15);    // Remove fractions and clamp to defined range
noteMin = min(max(slider31 | 0, 0), 127);          //
noteMax = min(max(slider32 | 0, 0), 127);          //                     |
velPercent = min(max(slider33 | 0, 0),20);         //                     |
transpose[0] = 12 * min(max(slider37 | 0, -2), 2); //                    \ /
channelOut[0] = min(max(slider38 - 1 | 0, 0), 15); //                     v
transpose[1] = 12 * min(max(slider40 | 0, -2), 2); //
channelOut[1] = min(max(slider41 - 1 | 0, 0), 15); //
transpose[2] = 12 * min(max(slider43 | 0, -2), 2); //
channelOut[2] = min(max(slider44 - 1 | 0, 0), 15); //
transpose[3] = 12 * min(max(slider46 | 0, -2), 2); //
channelOut[3] = min(max(slider47 - 1 | 0, 0), 15); //
transpose[4] = 12 * min(max(slider49 | 0, -2), 2); //
channelOut[4] = min(max(slider50 - 1 | 0, 0), 15); //
transpose[5] = 12 * min(max(slider52 | 0, -2), 2); //
channelOut[5] = min(max(slider53 - 1 | 0, 0), 15); //
transpose[6] = 12 * min(max(slider55 | 0, -2), 2); //
channelOut[6] = min(max(slider56 - 1 | 0, 0), 15); //
  
isMode ? (
  i = 0;
  bit = scale & $x01;
  while (
    scale = scale >> 1 | bit << 11;
    bit = scale & $x01;
    bit ? i += 1;
    i != mode;
  );
);

scale = scale | scale << 12 | (scale & $xFF) << 24;

@block    ///////////////////////////////////

while (
  midirecv(offset ,msg1 ,msg23) ? (
    status = msg1 & $xF0;
    channel = msg1 & $x0F;
    note = msg23 & $x7F;
    velocity = msg23 >> 8;
    
    channel == inChannel || inChannel == -1 ? (
      noteidx = (note - key) % 12;
      root = note - noteidx;

      status == statNoteOn || status == statNoteOff ? (
        scale & (2^noteidx) ? (
          midisend(offset, status + channelOut[0], (note + transpose[0] | velocity << 8));
          note >= noteMin && note <= noteMax && isHarmony ? (
            i = 0;
            j = noteidx;
            loop (harmony,
              k = 0;
              while (
                j += 1;
                scale & (2^j) ? k += 1;
                k != 2;
              );
              i += 1;
              noteOut = root + j + transpose[i];
              noteOut >= 0 && noteOut <= 127 ? (    //If note out of range don't send it
                velocityOut = velocity * ((100 - rand(velPercent)) * 0.01) | 0;
                midisend(offset, status + channelOut[i], (noteOut | velocityOut << 8));
              );
            );
          );
        );
      ):(      //  Pass through original message if not a note event
        midisend(offset, msg1, msg23);
      );
    ):(        //  Pass through original message if not on specified channel
      midisend(offset, msg1, msg23);
    );
    doh = 1;   // Force loop to continue until all messages read
  );
);
