desc: 3-zones MIDI keyboard splitter

//
// 3-zones MIDI keyboard splitter by splisp
//
// Copyright (C) 2012 splisp (Fabrizio Benedetti) <fabrizio.benedetti@email.it>
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
//


in_pin: none
out_pin: none


slider1:0<0,15,1{Channel1,Channel2,Channel3,Channel4,Channel5,Channel6,Channel7,Channel8,Channel9,Channel10,Channel11,Channel12,Channel13,Channel14,Channel15,Channel16>Input channel
slider2:1<0,15,1{Channel1,Channel2,Channel3,Channel4,Channel5,Channel6,Channel7,Channel8,Channel9,Channel10,Channel11,Channel12,Channel13,Channel14,Channel15,Channel16>Lower output channel
slider3:0<-24,24,1>Lower transpose
slider4:48<1,126,1>Split point 1
slider5:2<0,15,1{Channel1,Channel2,Channel3,Channel4,Channel5,Channel6,Channel7,Channel8,Channel9,Channel10,Channel11,Channel12,Channel13,Channel14,Channel15,Channel16>Middle output channel
slider6:0<-24,24,1>Middle transpose
slider7:72<2,127,1>Split point 2
slider8:3<0,15,1{Channel1,Channel2,Channel3,Channel4,Channel5,Channel6,Channel7,Channel8,Channel9,Channel10,Channel11,Channel12,Channel13,Channel14,Channel15,Channel16>Upper output channel
slider9:0<-24,24,1>Upper transpose
slider10:0<0,3,1{Filter,Lower,Middle,Upper,All}>Non-note messages


//----------
@init 
//----------
  // Constants
  stNoteOn = $x90;
  stNoteOff = $x80;
  stCtrl = $xB0;
  ccAllNotesOff = 123;
  nnFilter = 0;
  nnLower = 1;
  nnMiddle = 2;
  nnUpper = 3;
  nnAll = 4;
  UNCHANGED = 256; // -1 wouldn't do for signed values!

  // Status
  InputChannel = 0;
  LowerOutputChannel = 0;
  LowerTranspose = 0;
  SplitPoint1 = 48;
  MiddleOutputChannel = 1;
  MiddleTranspose = 0;
  SplitPoint2 = 72;
  UpperOutputChannel = 2;
  UpperTranspose = 0;
  NonNotes = nnFilter;

  // In order to avoid hung notes, send AllNotesOff when there is the possibility of NoteOff
  // messages being lost; since midisend() doesn't work in the @slider section, we have to
  // defer sending AllNotesOff to the @block section
  HasDeferredEvents = 0;
  NewInputChannel = UNCHANGED;
  NewLowerOutputChannel = UNCHANGED;
  NewLowerTranspose = UNCHANGED;
  NewSplitPoint1 = UNCHANGED;
  NewMiddleOutputChannel = UNCHANGED;
  NewMiddleTranspose = UNCHANGED;
  NewSplitPoint2 = UNCHANGED;
  NewUpperOutputChannel = UNCHANGED;
  NewUpperTranspose = UNCHANGED;


//----------
@slider
//----------
  // Check Splits against each other
  (slider4 >= slider7) ?
  ( // Don't allow SplitPoint1 to be >= SplitPoint2
    (slider4 != SplitPoint1) ?
    ( // slider4 has been moved
      slider4 = slider7 - 1;
    )
    :
    ( // slider7 has been moved
      slider7 = slider4 + 1;
    );
  );

  HasDeferredEvents = 1;

  (InputChannel != slider1) ?
    NewInputChannel = slider1;

  (LowerOutputChannel != slider2) ?
    NewLowerOutputChannel = slider2;

  (LowerTranspose != slider3) ?
    NewLowerTranspose = slider3;

  (SplitPoint1 != slider4) ?
    NewSplitPoint1 = slider4;

  (MiddleOutputChannel != slider5) ?
    NewMiddleOutputChannel = slider5;

  (MiddleTranspose != slider6) ?
    NewMiddleTranspose = slider6;

  (SplitPoint2 != slider7) ?
    NewSplitPoint2 = slider7;

  (UpperOutputChannel != slider8) ?
    NewUpperOutputChannel = slider8;

  (UpperTranspose != slider9) ?
    NewUpperTranspose = slider9;

  NonNotes = slider10;


//----------
@block
//----------
  // Process deferred events
  (HasDeferredEvents) ?
  (
    (NewInputChannel != UNCHANGED) ?
    (
      midisend(0, stCtrl | LowerOutputChannel, ccAllNotesOff);
      midisend(0, stCtrl | MiddleOutputChannel, ccAllNotesOff);
      midisend(0, stCtrl | UpperOutputChannel, ccAllNotesOff);
      InputChannel = NewInputChannel;
      NewInputChannel = UNCHANGED;
    );

    (NewLowerOutputChannel != UNCHANGED) ?
    (
      midisend(0, stCtrl | LowerOutputChannel, ccAllNotesOff);
      LowerOutputChannel = NewLowerOutputChannel;
      NewLowerOutputChannel = UNCHANGED;
    );

    (NewLowerTranspose != UNCHANGED) ?
    (
      midisend(0, stCtrl | LowerOutputChannel, ccAllNotesOff);
      LowerTranspose = NewLowerTranspose;
      NewLowerTranspose = UNCHANGED;
    );
    
    (NewSplitPoint1 != UNCHANGED) ?
    (
      midisend(0, stCtrl | LowerOutputChannel, ccAllNotesOff);
      midisend(0, stCtrl | MiddleOutputChannel, ccAllNotesOff);
      SplitPoint1 = NewSplitPoint1;
      NewSplitPoint1 = UNCHANGED;
    );

    (NewMiddleOutputChannel != UNCHANGED) ?
    (
      midisend(0, stCtrl | MiddleOutputChannel, ccAllNotesOff);
      MiddleOutputChannel = NewMiddleOutputChannel;
      NewMiddleOutputChannel = UNCHANGED;
    );

    (NewMiddleTranspose != UNCHANGED) ?
    (
      midisend(0, stCtrl | MiddleOutputChannel, ccAllNotesOff);
      MiddleTranspose = NewMiddleTranspose;
      NewMiddleTranspose = UNCHANGED;
    );
    
    (NewSplitPoint2 != UNCHANGED) ?
    (
      midisend(0, stCtrl | MiddleOutputChannel, ccAllNotesOff);
      midisend(0, stCtrl | UpperOutputChannel, ccAllNotesOff);
      SplitPoint2 = NewSplitPoint2;
      NewSplitPoint2 = UNCHANGED;
    );

    (NewUpperOutputChannel != UNCHANGED) ?
    (
      midisend(0, stCtrl | UpperOutputChannel, ccAllNotesOff);
      UpperOutputChannel = NewUpperOutputChannel;
      NewUpperOutputChannel = UNCHANGED;
    );

    (NewUpperTranspose != UNCHANGED) ?
    (
      midisend(0, stCtrl | UpperOutputChannel, ccAllNotesOff);
      UpperTranspose = NewUpperTranspose;
      NewUpperTranspose = UNCHANGED;
    );

    HasDeferredEvents = 0;
  );

  // Process incoming MIDI messages
  while
  (
    midirecv(offset, msg1, msg23) ?
    (
      chn = msg1 & $x0F;
      (chn == InputChannel) ?
      (
        status = msg1 & $xF0;
        ((status == stNoteOn) || (status == stNoteOff)) ?
        ( // Note messages
          note = msg23 & $x7F;
          vel = msg23 & $x7F00;
          (note < SplitPoint1) ?
          (
            note += LowerTranspose;
            ((note >= 0) && (note <= 127)) ?
              midisend(offset, status | LowerOutputChannel, vel | note);
          )
          :
          (
            (note < SplitPoint2) ?
            (
              note += MiddleTranspose;
              ((note >= 0) && (note <= 127)) ?
                midisend(offset, status | MiddleOutputChannel, vel | note);
            )
            :
            (
              note += UpperTranspose;
              ((note >= 0) && (note <= 127)) ?
                midisend(offset, status | UpperOutputChannel, vel | note);
            );
          );
        )
        :
        (
          // Non-note messages
          ((NonNotes == nnLower) || (NonNotes == nnAll)) ? midisend(offset, status | LowerOutputChannel, msg23);
          ((NonNotes == nnMiddle) || (NonNotes == nnAll)) ? midisend(offset, status | MiddleOutputChannel, msg23);
          ((NonNotes == nnUpper) || (NonNotes == nnAll)) ? midisend(offset, status | UpperOutputChannel, msg23);
        );
      );
      1; // Force while continuation
    );
  );
