{ Contact: support@robertinventor.com
  Web page: http://robertinventor.com/ftswiki/High_numbered_keyswitches_retuning
  Copyright (c) 2013 Robert Walker
  This software is provided as-is, without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
  
  Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:

  1. The origin of this software must not be misrepresented; you must not    claim that you wrote the original software. If you use this software    in a product, an acknowledgment in the product documentation would be
   appreciated but is not required.
  
  2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
  
  3. This notice may not be removed or altered from any source distribution.    
  This is the ZLib license suitable for free software and also for open source software and commmercial use
  https://en.wikipedia.org/wiki/Zlib_License
}
 
{TO CONFIGURE
 If instrument has keyswitches then set the variables declared at the start of the "on init" handler

 TO MERGE WITH EXISTING SCRIPTS
 
 * Add the code from the on init, on note, on release and on controller  handlers to your own handlers
 * Copy all the other routines into your code. 
 * You may want to reposition the $vksr_ResetTo12EQ control
 
 All variables and routines start vskr_ so chance of variable collision is low
 If you do get a variable collision, simply change the  prefix with search and replace of "vskr_"
 in this code before you do the merge
 } 
 
on init
    
 set_script_title("Keyswitch Retuning")  
 
 declare $vksr_instrument_has_keyswitches_below :=0  { If instrument has keyswitches below its range. set this to lowest playable note, defaults to 0 }
 declare $vksr_instrument_has_keyswitches_above :=128 { If instrument has keyswitches above its range, set this to highest playable note, defaults to 128 }

 declare $vksr_keyswitchv2_retuning_byte_position:=0 
  { gets set to e.g. 4 for 4 byte retuning
  -  gets decremented as data is received, retunes when reaches 0
  }
 declare $vksr_keyswitch_instruction:=0 { used for "running status" }
 declare $vksr_keyswitchv2_extra_fine_pitch_bend_byte_position := 0
 declare $vksr_midi_tuning_enabled:=0
 declare $vksr_show_diagnostics := 1 {this shows various messages for debugging and diagnostic purposes, can be set to 0 for release }
 declare  @vksr_current_instruction_string 
 @vksr_current_instruction_string := ""
 declare %vksr_pitch_bend_for_output_note[128]
 declare %vksr_output_note[128]
 declare %vksr_output_note_at_note_on[128]
 declare %vksr_is_active[128]
 declare %vksr_note_id[128]
 declare $vksr_EVENT_NOTE:=0
 declare $vksr_EVENT_VELOCITY:=0
 declare $vskr_CC_NUM:=0
 declare $vksr_input_note := 0
 declare $vksr_next_output_note := 0
 declare $vksr_next_pitch_bend_coarse := 0
 declare $vksr_next_pitch_bend_fine := 0
 declare $vksr_next_pitch_bend_extra_fine := 0
 declare $vksr_pitch_bend_combined := 0
 declare $vksr_note_shift := 0
 declare $vksr_note_on_id := 0
 declare $vksr_input_pitch_bend_0_at := 8192
 declare $vksr_input_pitch_steps_in_semitone := 16384 { Use full pitch bend range to adjust from -50 to 50 cents }
 declare $vksr_cc119_was:=0
 declare $vksr_result:= 0 
 { these next are local temporary variables except get syntax error messages in Kontakt 5 if I try to declare them
  within a function so declaring them here as globals, should be okay since midi is serial
 }
 declare $vksr_ready_to_retune := 0 
 declare $vksr_parsed :=0
 declare $vksr_i := 0
 declare  $vskr_data_value:=0 

 declare ui_button $vksr_ResetTo12EQ
 $vksr_ResetTo12EQ:=1
 set_text ($vksr_ResetTo12EQ,"Reset to 12-equal")

 make_persistent (%vksr_pitch_bend_for_output_note)
 make_persistent (%vksr_output_note)
 declare $vksr_normal_group { normal groups } 
 
 {INLINED vksr_reset_to_12et}
 
 $vksr_i := 0
 while ($vksr_i<128)
       
  %vksr_is_active[$vksr_i] := 0
  %vksr_note_id[$vksr_i] := 0
  %vksr_output_note_at_note_on[$vksr_i] := $vksr_i
  
  { This has no effect if these are set to make_persistent }
  %vksr_pitch_bend_for_output_note[$vksr_i] := 0
  %vksr_output_note[$vksr_i] := $vksr_i
  $vksr_i := $vksr_i+1
   
 end while
 
 {END vksr_reset_to_12et}
 
 if($vksr_show_diagnostics#0)
  message("on init")
 end if 

end on { on init }

function vksr_reset_to_12et
 $vksr_i := 0  
 while ($vksr_i<128)
       
  %vksr_is_active[$vksr_i] := 0
  %vksr_note_id[$vksr_i] := 0
  %vksr_pitch_bend_for_output_note[$vksr_i] := 0
  %vksr_output_note[$vksr_i] := $vksr_i
  %vksr_output_note_at_note_on[$vksr_i] := $vksr_i
  $vksr_i := $vksr_i+1
    
 end while
  
 $vksr_next_pitch_bend_extra_fine:= 0
 $vksr_keyswitch_instruction:=0
 $vksr_keyswitchv2_retuning_byte_position:= 0
  
 $vksr_ResetTo12EQ:=1
   
end function


on ui_control ($vksr_ResetTo12EQ)
    
  $vksr_ResetTo12EQ := 1 {press the "reset to 12 et" button}

 { This button cant be unpressed.
   Idea is to show as pressed on init, or if reset to 12 et either through 
   pressing this button or through midi in event.. 
   gets unpressed when you change the tuning of one of the notes.
   - note, if you adjust the notes individually to 12-et then wont show it as pressed
  }

  call vksr_reset_to_12et
 
end on { on ui_control ($vksr_ResetTo12EQ) }

function vksr_retune_currently_active_note
 { If $vksr_input_note is currently active, retune it immediately }
 if (%vksr_is_active[$vksr_input_note] # 0)
  if (%vksr_output_note_at_note_on[$vksr_input_note]=%vksr_output_note[$vksr_input_note])
   change_tune(%vksr_note_id[$vksr_input_note],%vksr_pitch_bend_for_output_note[$vksr_input_note],0)
  else
   $vksr_note_shift := %vksr_output_note[$vksr_input_note]-%vksr_output_note_at_note_on[$vksr_input_note]
   change_tune(%vksr_note_id[$vksr_input_note],%vksr_pitch_bend_for_output_note[$vksr_input_note]+($vksr_note_shift*100000),0)
  end if
 end if
 
end function

function vksr_diagnostics_message_for_current_instruction

 if($vksr_show_diagnostics # 0) 
  message(@vksr_current_instruction_string) {comment this out if you want to see the @vksr_messages_events_string diagnostics}
 end if

end function

function vksr_process_tuning_keyswitches
 $vksr_ready_to_retune:=0
 $vksr_parsed:=0
 
 $vskr_data_value:=-1
 if ($vksr_EVENT_NOTE=126)
  $vskr_data_value:=$vksr_EVENT_VELOCITY
 end if
 if ($vksr_EVENT_NOTE=127 and $vksr_EVENT_VELOCITY=127)
  $vskr_data_value:=0
 end if
 
 if ($vksr_midi_tuning_enabled = 119) {enable_high_note_keyswitches_retuning_v2 }

 { CHECK FOR INSTRUCTIONS FOR 126, 127 METHOD - E.G. 127, 4 FOR COMPLETE RETUNING MESSAGE FOR A SINGLE NOTE }
 { 127, 126 = Reset to 12-et
   127,4 = Retune instruction (I chose 4 because it reminds you how many data bytes follow)  
   126, input note
   126, output note
   126, coarse pitch bend
   126, fine pitch bend
    127, 127 = data value 0
  }

  if ($vksr_EVENT_NOTE#127 and $vksr_EVENT_NOTE#126)
   { not recognized as instruction or data - so stop processing any instruction in progress }
   $vksr_keyswitch_instruction:=0      
   $vksr_keyswitchv2_retuning_byte_position:=0 
  end if { $vksr_EVENT_NOTE#127 and $vksr_EVENT_NOTE#126 }
  
  if ($vksr_EVENT_NOTE=127 and $vksr_EVENT_VELOCITY#127)
     { "Instructions - do 127, 127 for data 0 case separately }
   $vksr_keyswitch_instruction:=$vksr_EVENT_VELOCITY  
   $vksr_keyswitchv2_retuning_byte_position:=0
   
   if($vksr_keyswitch_instruction=11)
   { 127,11 = set extra fine pitch adjustment for next note to be retuned
     126, e
    }
    $vksr_keyswitchv2_extra_fine_pitch_bend_byte_position := 1
    if($vksr_show_diagnostics#0)
     @vksr_current_instruction_string := @vksr_current_instruction_string & " pitch bend extra fine"
    end if  
    $vksr_parsed:=1
   end if { $vksr_keyswitch_instruction = 11 }
   
   if($vksr_keyswitch_instruction=126) { note 127, vel 126 = Reset to 12-et}
    call vksr_reset_to_12et
    $vksr_parsed:=1
   end if
   
   if($vksr_keyswitch_instruction = 4)  {4 byte Retune instruction }
    $vksr_keyswitchv2_retuning_byte_position:=4
    if($vksr_show_diagnostics#0)
     @vksr_current_instruction_string := "retune " & $vksr_keyswitchv2_retuning_byte_position & " bytes "
    end if
    $vksr_parsed:=1
   end if
   if($vksr_keyswitch_instruction = 3) 
    if($vksr_show_diagnostics#0)
     @vksr_current_instruction_string := "retune " & $vksr_keyswitchv2_retuning_byte_position & " bytes "
    end if    
    $vksr_keyswitchv2_retuning_byte_position:=3
    $vksr_parsed:=1
   end if
   if($vksr_keyswitch_instruction = 2) 
    $vksr_keyswitchv2_retuning_byte_position:=2
    @vksr_current_instruction_string := "retune " & $vksr_keyswitchv2_retuning_byte_position & " bytes "
    $vksr_parsed:=1
   end if
   if($vksr_keyswitch_instruction = 1) 
    $vksr_keyswitchv2_retuning_byte_position:=1
    if($vksr_show_diagnostics#0)
     @vksr_current_instruction_string := "retune " & $vksr_keyswitchv2_retuning_byte_position & " bytes "
    end if   
    $vksr_parsed:=1
   end if
   if($vksr_keyswitch_instruction = 127) 
    $vksr_parsed:=1 { data value zero, processed below }
   end if
   
   if($vksr_parsed# 0)
    $vksr_ResetTo12EQ:=0
   end if 
  
   if($vksr_parsed = 0)
    { not recognized as instruction or data - so stop processing any instruction in progress }
    $vksr_keyswitchv2_retuning_byte_position:=0
    $vksr_keyswitch_instruction:=0
    if($vksr_show_diagnostics # 0 and $vksr_keyswitch_instruction #0)
     @vksr_current_instruction_string := " " & @vksr_current_instruction_string & " End tuning (instr: "  & $vksr_EVENT_VELOCITY  & ")"
    end if
   end if { $vksr_parsed = 0 }

  end if {$vksr_EVENT_NOTE=127 and $vksr_EVENT_VELOCITY#127}
     
  if($vksr_keyswitch_instruction = 100) { end tuning and switch off tuning method}
   if($vksr_show_diagnostics#0)

    @vksr_current_instruction_string := @vksr_current_instruction_string & " - switch of midi retuning "

   end if
   $vksr_midi_tuning_enabled:=0
  end if
  
  { CHECK FOR EXTRA FINE PITCH BEND FOR 126, 127 METHOD }
  
  if($vksr_keyswitchv2_extra_fine_pitch_bend_byte_position=1)
   if ($vskr_data_value>=0)
    $vksr_next_pitch_bend_extra_fine:=$vskr_data_value
    $vksr_ready_to_retune:=1
   end if
   $vksr_keyswitchv2_extra_fine_pitch_bend_byte_position:=0
   if($vksr_show_diagnostics # 0)
    @vksr_current_instruction_string := @vksr_current_instruction_string & "extra fine pb: " & $vksr_keyswitchv2_extra_fine_pitch_bend_byte_position
   end if 
  end if { $vksr_keyswitchv2_extra_fine_pitch_bend_byte_position=1 }
  
  
  { CHECK FOR DATA FOR 126, 127 METHOD - THIS WILL BE EITEHR 126, N OR 127, 127 FOR DATA VALUE 0 }
  if($vksr_keyswitchv2_retuning_byte_position#0)
   
   { Data byte for input note, output note, coarse or fine pitch bend
     depending on  $vksr_keyswitchv2_retuning_byte_position 
   }
   
   if ($vskr_data_value>=0)
        { non zero values as 126,value}
    if($vksr_keyswitchv2_retuning_byte_position= 4) {input note}
     $vksr_input_note := $vskr_data_value
     if($vksr_show_diagnostics # 0)
      @vksr_current_instruction_string := @vksr_current_instruction_string & " input note: " & $vskr_data_value
     end if
    end if
    if($vksr_keyswitchv2_retuning_byte_position= 3) {output note}
     $vksr_next_output_note := $vskr_data_value
     if($vksr_show_diagnostics # 0)
      @vksr_current_instruction_string := @vksr_current_instruction_string & " output note: " & $vskr_data_value
     end if
    end if
    if($vksr_keyswitchv2_retuning_byte_position= 2) 
     $vksr_next_pitch_bend_coarse := $vskr_data_value
     $vksr_next_pitch_bend_extra_fine:=0
     if($vksr_show_diagnostics # 0)
      @vksr_current_instruction_string := @vksr_current_instruction_string & " pb coarse: " & $vskr_data_value
     end if
    end if
    if($vksr_keyswitchv2_retuning_byte_position= 1) 
     $vksr_next_pitch_bend_fine := $vskr_data_value
     $vksr_next_pitch_bend_extra_fine:=0
     $vksr_next_pitch_bend_extra_fine:=0
     if($vksr_show_diagnostics # 0)
      @vksr_current_instruction_string := @vksr_current_instruction_string & " pb fine: " & $vskr_data_value
     end if
    end if
    { After data recieved - Decrement byte position}
    $vksr_keyswitchv2_retuning_byte_position:=$vksr_keyswitchv2_retuning_byte_position-1
    { If tuning position is zero, then ready to retune }
    if ($vksr_keyswitchv2_retuning_byte_position = 0)
     $vksr_ready_to_retune:=1 { actual retuning done below }
     $vksr_keyswitchv2_retuning_byte_position:=$vksr_keyswitch_instruction
     { example 4 for 127, 4, can then keep resending the data in groups of 4 as input, output, fine, coarse dont need to keep resending
       the insruction as well
     }
    end if
   end if {$vksr_EVENT_NOTE=126 or ($vksr_EVENT_NOTE= 127 and $vksr_EVENT_VELOCITY= 127) }
  end if { $vksr_keyswitchv2_retuning_byte_position#0 }
  
  if($vksr_ready_to_retune#0)
   
   if($vksr_show_diagnostics # 0) 
    @vksr_current_instruction_string:=@vksr_current_instruction_string & " RETUNED "
   end if

   %vksr_output_note[$vksr_input_note] := $vksr_next_output_note  

   { This is the simpler calculation of pitch bend in millicents from input pitch bend }
   $vksr_pitch_bend_combined := $vksr_next_pitch_bend_coarse*128+$vksr_next_pitch_bend_fine
   %vksr_pitch_bend_for_output_note[$vksr_input_note] :=  1000*100*($vksr_pitch_bend_combined-$vksr_input_pitch_bend_0_at)/($vksr_input_pitch_steps_in_semitone)
 
   { This is the calculation to do to take accout of the extra fine byte for the pitch bend }
   $vksr_pitch_bend_combined := $vksr_next_pitch_bend_coarse*128+$vksr_next_pitch_bend_fine
   { Ideally would do this, but Kontakt values are only integers not floating point so would overflow:
     $vksr_pitch_bend_combined := ($vksr_next_pitch_bend_coarse*128+$vksr_next_pitch_bend_fine)*128+$vksr_next_pitch_bend_extra_fine
     %vksr_pitch_bend_for_output_note[$vksr_input_note] := 1000*100*($vksr_pitch_bend_combined-$vksr_input_pitch_bend_0_at*128)/($vksr_input_pitch_steps_in_semitone*128)
   }
   { So instead do this - which could be out by perhaps 1 millicent from the correct value: }
   %vksr_pitch_bend_for_output_note[$vksr_input_note] :=  1000*100*($vksr_pitch_bend_combined-$vksr_input_pitch_bend_0_at)/($vksr_input_pitch_steps_in_semitone)
   %vksr_pitch_bend_for_output_note[$vksr_input_note] :=  %vksr_pitch_bend_for_output_note[$vksr_input_note] + (1000*100*$vksr_next_pitch_bend_extra_fine)/($vksr_input_pitch_steps_in_semitone*128)
   $vksr_next_pitch_bend_extra_fine := 0 { have used it, so reset now, dont want it to carry over to next note}

   call vksr_retune_currently_active_note 
  end if { $vksr_ready_to_retune#0 }
 end if  { $vksr_midi_tuning_enabled = 119 enable_high_note_keyswitches_retuning_v2}
  
 if($vksr_show_diagnostics # 0)
  call vksr_diagnostics_message_for_current_instruction
 end if 
 
end function

function vksr_retune_note_on  
      
 %vksr_is_active[$vksr_EVENT_NOTE] := 1
 { Record the output note now, to use to work out the total pitch bend later on
 using the change of output note since the note on
 }
 %vksr_output_note_at_note_on[$vksr_EVENT_NOTE] := %vksr_output_note[$vksr_EVENT_NOTE]
  
 {Check that the note is not a keyswitch after retuning - if it is
  then is "out of range silent"
 }
 if($vksr_instrument_has_keyswitches_below<=%vksr_output_note[$vksr_EVENT_NOTE] and %vksr_output_note[$vksr_EVENT_NOTE] <=$vksr_instrument_has_keyswitches_above)
  $vksr_note_on_id := play_note(%vksr_output_note[$vksr_EVENT_NOTE],$vksr_EVENT_VELOCITY,0,-1)
   
  change_tune($vksr_note_on_id,%vksr_pitch_bend_for_output_note[$vksr_EVENT_NOTE],0)
  %vksr_note_id[$vksr_EVENT_NOTE] := $vksr_note_on_id
  if($vksr_show_diagnostics#0)
   message("Note " & $vksr_EVENT_NOTE & " as " & %vksr_output_note[$vksr_EVENT_NOTE] ...
   & ", bend: " & %vksr_pitch_bend_for_output_note[$vksr_EVENT_NOTE] & " millicents, " & " Velocity " & $vksr_EVENT_VELOCITY)
  end if
  change_tune($vksr_note_on_id,%vksr_pitch_bend_for_output_note[$vksr_EVENT_NOTE],0)
  %vksr_note_id[$vksr_EVENT_NOTE] := $vksr_note_on_id
 else
  message("OUT OF RANGE note -  " & $vksr_EVENT_NOTE & " -> " ...
  & %vksr_output_note[$vksr_EVENT_NOTE] & " - IGNORED  - INSTRUMENT RANGE " &$vksr_instrument_has_keyswitches_below ...
  & " to " & $vksr_instrument_has_keyswitches_above...
  )    
 end if
 
end function

function vksr_on_controller

 if($vskr_CC_NUM = 119)
 
  {on receipt of %vksr_CC[119], switches midi tuning on if %vksr_CC[119] = 119 and otherwise switches it off}
  $vksr_midi_tuning_enabled:=%CC[119]

 end if

end function

on note
    
 $vksr_EVENT_NOTE:=$EVENT_NOTE
 $vksr_EVENT_VELOCITY:=$EVENT_VELOCITY
        
 if ($vksr_midi_tuning_enabled = 119 and $EVENT_NOTE >=126 )
  {enable_high_note_keyswitches_retuning_v2 or enable_high_note_keyswitches_retuning }

  ignore_event($EVENT_ID)
  call vksr_process_tuning_keyswitches
  
 else  
  if($vksr_instrument_has_keyswitches_below<=$vksr_EVENT_NOTE and $vksr_EVENT_NOTE <=$vksr_instrument_has_keyswitches_above)
  
   ignore_event($EVENT_ID)
   call vksr_retune_note_on  
   
  end if
   
 end if

end on { on note }

on release
    
 %vksr_is_active[$EVENT_NOTE] := 0
 %vksr_note_id[$EVENT_NOTE] := 0
  
end on

on controller
 
 $vskr_CC_NUM:=$CC_NUM
 call vksr_on_controller
 
end on