Contents

Overview

KScript Editor is a text editor specifically written to make it easier to write and work with Kontakt 2[1] scripts. It also features an integrated script compiler, so it's really an IDE. The editor is based on Scintilla - an open source text editor component.

Note: this documentation is not complete. I will add bits and pieces when I find time to do so.

Extended script syntax

Although it's possible to use just the editing facilities of the editor and copy and paste the code to Kontakt 2, it's also possible to compile your scripts in the editor by pressing F5. By doing so you can check for errors (activate the Extra syntax checks option for even more elaborate error checking) and you can use an extended script language syntax which makes it easier to write and maintain scripts.

The following sections will explain the various extensions to the native KSP syntax that the KScript Editor allows you to use. To translate a script from the extended syntax which is easier to read and work with to the original syntax that Kontakt 2 understands you press the F5 key in KScript Editor. This compiles the script, ie. translates the extended syntax to ordinary KSP syntax. If the compilation was successful the compiled code is automatically placed on the clipboard so you can just go ahead and paste it into Kontakt 2. In the examples below the yellow code samples represents code written with the extended syntax and the gray boxes what the corresponding compiled code looks like.

Variable prefixes are optional

In normal KSP it's mandatory to prefix variables with one of the characters $, %, @ and !. With the extended syntax this is not necessary. The only case where a prefix is necessary is on the declaration line of a string variable or string array variable. No prefixes are necessary when the variables are simply used. Example:

declare x := a + b + c
declare list[4] 
declare @name  
name := 'sustains'
declare $x := $a + $b + $c
declare %list[4]
declare @name   
@name := 'sustains'

Parenthesis are optional for if, while and select

In an effort to make the source code slightly more readable parenthesis are optional for if statements, while-loops and select statements.

while x <= 10
if x = 1       
select x       
while (x <= 10)  
if (x = 1)       
select (x)       

Note: if the condition starts with a left parenthesis but does not end with a right parenthesis this will at the moment confuse the compiler so for the time being you need to wrap the whole expression in parenthesis in that specific case.

For-loops

KSP only supports while-loops but with the extended syntax you can also use for-loops. Example:

for i := 0 to 9
  list[i] := 1
end for
$i := 0
while ($i <= 9)
  %list[$i] := 1
  inc($i)
end while

It's also possible to loop downwards and/or optionally use a certain step size:

for i := 9 downto 0 step 2
  list[i] := 1
end for
$i := 9
while ($i >= 0)
  %list[$i] := 1
  $i := $i - 2
end while

Else If

The extended syntax provides an "else if" construct since this is lacking in KSP.

if x = 1
  {...}
else if y = 1  
  {...}
else if z = 1  
  {...}
end if
if ($x = 1)
  {...}
else
if ($y = 1)
  {...}
else
if ($z = 1)
  {...}
end if
end if
end if

Variable families

With KSP there is no good way to organize variables so there tends to be a huge list of variable declarations in the init callback which makes it hard to know what is used where. With the extended syntax you can declare variables which belong to the same category in a family and then refer to them as family.variable (the family name followed by a period followed by the variable name). This can make variable names slightly longer but makes it easier to quickly grasp what a variable is used for. In the compiled script the dots are replaced by two underscores.

Note that after the declaration of a variable inside a family you always have to use the fully qualified name to refer to it. For example, in the declaration of keys below one has to use keyswitch.N instead of just N. It is also possible to nest families.

on init
  family keyswitch
    declare current
    declare const N := 10    
    declare keys[keyswitch.N]
  end family
end on

on note
  keyswitch.current := search(keyswitch.keys, 
                              EVENT_NOTE)
end on
on init
  {family keyswitch}
    declare $keyswitch__current
    declare const $keyswitch__N := 10    
    declare %keyswitch__keys[$keyswitch__N]
end on

on note
  $keyswitch__current := search(%keyswitch__keys,
                                $EVENT_NOTE)
end on

User-defined functions

KSP features a number of builtin functions, eg. play_note and random. A common theme for all functions is that they perform some kind of operation for you, without you needing to know the exact details of how they do it, and you can pass parameters to them to control how they should work. For example, when you call play_note you pass the note to play, the velocity to use and some other things.

The extended syntax allows you to define your own functions. The sample script below is a simple humanization script which adds a random number between -10 and 10 to incoming velocities. The limit_range function is used to clip the final velocity value to the range 1 to 127.

on init
  declare velocity
end on

on note
  velocity := EVENT_VELOCITY + random(-10, 10)
  limit_range(velocity, 1, 127)
  change_velo(EVENT_ID, velocity)
end on

{ forces value to be between min and max }
function limit_range(value, min, max)      
  if value < min
    value := min
  end if
  if value > max
    value := max
  end if
end function
on init
  declare $velocity
end on

on note
  $velocity := $EVENT_VELOCITY + random(-10, 10)
  {begin limit_range($velocity,1,127)}
    if ($velocity < 1)
      $velocity := 1
    end if
    if ($velocity > 127)
      $velocity := 127
    end if
  {end limit_range($velocity,1,127)}
  change_velo($EVENT_ID, $velocity)
end on

Please compare the uncompiled and compiled code. Since KSP has no support for user-defined functions all function invokations need to be replaced by the body of the function upon compilation. Note how the parameters are inserted into the compiled code: any occurance of value, min and max in the function body is replaced by the parameters velocity, 1 and 127 respectively. From within the body of one function it is possible to call another function, but since the body of invoked functions have to be inlined at some point it is not allowed for a function to directly or indirectly call itself.

For functions which have no parameters it's preferred to leave out the parenthesis alltogether when you declare or call them. It is also possible, but not recommended, to use a pair of empty parenthesis like in languages like Java and C++. Example:

function do_something   {recommended syntax}
function do_something() {also allowed}

If you declare a variable inside a function it is by default considered local to that function. This means that the function has its own copy of the variable so even if a variable with the same name was declared in 'on init' or some other function they won't interfer with each other. Local variables are prefixed with an underscore upon compilation (see $_tmp below). If you want a variable declared inside a function to be accessible from callbacks and other functions you can either declare it like "declare global $x" or make sure the function name starts with "on_init" and all variables inside that function will implicitly be considered global.

on init
  declare x := 1
  declare y := 5
  swap(x, y)
end on

function swap(a, b)
  declare tmp
  tmp := a
  a := b
  b := tmp
end function
on init
  declare $x := 1
  declare $y := 5
  declare $_tmp
  {begin swap($x,$y)}
    $_tmp := $x
    $x := $y
    $y := $_tmp
  {end swap($x,$y)}
end on

Declaration order (advanced)
It's useful to know that local variables of a function which ends up not being used in a particular script are stripped from the compiled code. This makes it possible to build function libraries where unused functions don't clutter users' scripts with unnecessary variable declarations. In case several functions declare global variables and they are used in specific places in the callbacks or other functions it's good to be aware of the order in which these variables end up in the 'on init' callback (to make sure variables are declared before they are used for the first time):

  • For functions which are invoked directly or indirectly from the init callback the local/global variables end up at the point the function was invoked from the first time (any later invokations have no effect as far as variable declarations are concerned) in the order in which the functions were invoked.
  • For functions which are only invoked from other callbacks than the init callback declarations of global variables are placed at the top of the init callback and local variables at the bottom of the init callback in the order in which the corresponding functions were defined.

Pitfalls
If a function contains an expression like "5*x" and it is invoked with parameter x set to C+5 then the compiled code with be 5*C+5 and not 5*(C+5) as one might expect. As you see the compiler does not automatically insert parenthesis around C+5 so the user needs to either write 5*(x) in the function or pass the expression (C+5) as parameter.

Return values are not supported at this point. This limitation can to some extent be worked around by passing a parameter in which the function can store its return value.

Macros

The extended syntax allows you to use macros. These are in many ways similar to functions. However, whereas functions interpret the code inside the function body, eg. to support declaration of local variables, macros are used to just perform a very simple text substitution. Macros are inlined as the first compilation step. The differences between functions and macros are:

  • A macro may not invoke other macros.
  • Macro parameters can be used more freely, eg. in declare statements and as part of variable names (not inside strings however).
macro declare_button(#var#, #text#)
  declare ui_button #var#_button
  set_text(#var#_button, #text#)
end macro

on init
  declare_button(active, "Active")
end on
on init
  declare ui_button $active_button
  set_text($active_button, "Active")
end on

  • A macro definition may contain top-level constructs like callbacks and function definitions, in which case the macro may and must be invoked at the top-level (outside of callbacks/functions).

macro on_ui_control_do(#control#, #command#)
  on ui_control(#control#)
    #command#
  end on
end macro

on init
  declare ui_button active
end on

on_ui_control_do(active, message(active))
on init
  declare ui_button $active
end on

on ui_control($active)
  message($active)
end on

Hexademical numbers

The extended syntax allows you to use hexadecimal numbers if you prefix them by "0x".

x := 0xFF
x := 255

Import

It can be useful to be able to split up a script into separate files. The extended syntax allows you to bring in the functions and callbacks from such a script module using the import keyword. The following sample script imports all functions from the file "MyFunctions.txt" which is assumed to be placed in the same folder as the script importing it. This is equivalent to replacing the import line with the contents of the given file.

import "MyFunctions.txt"

It's also possible to import a module into its own namespace like this example shows. All variables then need to be prefixed with the given name followed by a dot (compare families). Importing modules this way ensures that there will be no variable name clashes with variables in the current script.

import "MyFunctions.txt" as funcs

on init
  funcs.on_init
end on

on note
  funcs.humanization_factor := 40
  funcs.randomize_note_velocity
end on

Pragma

It is possible to control how the compiler operates by using a pragma directive. On the surface it looks like a comment, but it is recognized by the compiler. At the moment there is only one use (but it may be extended in the future) - to instruct the compiler to save the compiled code in a file upon successful compilation. This is useful since it makes it easier to update the script source in Kontakt 4 which has a feature that lets you link the source code to a certain text file. Here is an example of how to have the compiler output the compiled code to a file:

on init
  {#pragma save_compiled_source D:\Program Files\Native Instruments\Kontakt 4\test.txt}
end on

For this pragma directive to have any effect the path needs to be absolute, contain both "Native Instruments" and "Kontakt 4", and end with ".txt". These conditions are safety precautions since any earlier text file with the name given will be overwritten upon compilation.
















[1] KONTAKT is a registered trademark of NATIVE INSTRUMENTS Software Synthesis GmbH. I am in no way affiliated with Native Instruments.