desc: 2-zones MIDI keyboard splitter

//
// 2-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:60<1,127,1>Split point
slider5:2<0,15,1{Channel1,Channel2,Channel3,Channel4,Channel5,Channel6,Channel7,Channel8,Channel9,Channel10,Channel11,Channel12,Channel13,Channel14,Channel15,Channel16}>Upper output channel
slider6:0<-24,24,1>Upper transpose
slider7:0<0,3,1{Filter,Lower,Upper,Both}>Non-note messages


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

  // Status
  InputChannel = 0;
  LowerOutputChannel = 0;
  LowerTranspose = 0;
  SplitPoint = 48;
  UpperOutputChannel = 1;
  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;
  NewSplitPoint = UNCHANGED;
  NewUpperOutputChannel = UNCHANGED;
  NewUpperTranspose = UNCHANGED;


//----------
@slider
//----------
  HasDeferredEvents = 1;

  (InputChannel != slider1) ?
    NewInputChannel = slider1;

  (LowerOutputChannel != slider2) ?
    NewLowerOutputChannel = slider2;

  (LowerTranspose != slider3) ?
    NewLowerTranspose = slider3;

  (SplitPoint != slider4) ?
    NewSplitPoint = slider4;

  (UpperOutputChannel != slider5) ?
    NewUpperOutputChannel = slider5;

  (UpperTranspose != slider6) ?
    NewUpperTranspose = slider6;

  NonNotes = slider7;


//----------
@block
//----------
  // Process deferred events
  (HasDeferredEvents) ?
  (
    (NewInputChannel != UNCHANGED) ?
    (
      midisend(0, stCtrl | LowerOutputChannel, 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;
    );
    
    (NewSplitPoint != UNCHANGED) ?
    (
      midisend(0, stCtrl | LowerOutputChannel, ccAllNotesOff);
      midisend(0, stCtrl | UpperOutputChannel, ccAllNotesOff);
      SplitPoint = NewSplitPoint;
      NewSplitPoint = 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 < SplitPoint) ?
          (
            note += LowerTranspose;
            ((note >= 0) && (note <= 127)) ?
              midisend(offset, status | LowerOutputChannel, vel | note);
          )
          :
          (
            note += UpperTranspose;
            ((note >= 0) && (note <= 127)) ?
              midisend(offset, status | UpperOutputChannel, vel | note);
          );
        )
        :
        ( // Non-note messages
          ((NonNotes == nnLower) || (NonNotes == nnBoth)) ? midisend(offset, status | LowerOutputChannel, msg23);
          ((NonNotes == nnUpper) || (NonNotes == nnBoth)) ? midisend(offset, status | UpperOutputChannel, msg23);
        );
      );
      1; // Force while continuation
    );
  );
