Skip to content
EvilDragon edited this page Feb 19, 2025 · 47 revisions

Introduction

SublimeKSP is a plugin for Sublime Text 3 and 4, developed by Nils Liberg and other contributors which makes working with Kontakt scripts much easier. It includes various KSP syntax extensions, as well as a transpiler to translate those extensions into valid KSP code. This fork was created to keep the plugin up-to-date with the latest versions of Kontakt and allow community contributions that enhance the plugin.

Basic Usage

Although it is possible to use just the standard editing facilities of Sublime Text and copy-paste the code to Kontakt, it is also possible to compile your scripts by pressing CmdK (macOS) and/or F5 on Windows/Linux. By doing so you can check for errors (activate the Extra Syntax Checks compiler 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.

You can switch to KSP syntax highlighting by pressing CmdShiftP (macOS) or CtrlShiftP (Windows/Linux), then typing "KSP" and selecting "Set Syntax: KSP" from the list of filtered commands. Alternatively, you can use the drop-down menu in the bottom right of Sublime Text's status bar.

Files with .ksp extension will load with KSP syntax highlighting automatically enabled. Files with .txt extension will do the same if the plugin succeeds at identifying it as a valid KSP script. Please note that the detection takes place as soon as you open a file - not when you save it!

When you open a file, the plugin will examine the end-of-line characters and automatically normalize them if line endings were incorrect previously. It will not automatically save changes in this case, so if a newly opened file looks as if it has been modified, it's because of this type of automatic EOL normalization.

SublimeKSP plugin- and compiler-related actions and options are found in Tools > SublimeKSP menu, which will be displayed only when a valid KSP file is focused in Sublime Text. You can also use Sublime Text's Command Palette and search for KSP. A more detailed explanation of these options can be found in the SublimeKSP Options section.

Here are some quick tips to get acquainted with the basic workflow in Sublime Text:

  • Press Tab to autocomplete the current variable or function name, or insert a snippet. Press CmdSpace (macOS) or CtrlSpace (Windows/Linux) to show the autocomplete menu.

  • Press CmdR (macOS) or CtrlR (Windows/Linux) in order to show an overview of all functions, callbacks, constant blocks, families, and sections in the script (this is called a "symbol list" in Sublime Text). Start typing a partial name of a symbol (the list is fuzzy searched) and press Enter to jump to it.

  • Press CmdK (macOS) or F5 on Windows/Linux in order to compile your script. The save_compiled_source pragma directive also accepts relative paths, which might be useful to know.

Extended Script Syntax

The following sections will explain the various extensions to the native KSP syntax that SublimeKSP allows you to use. There is a lot to go through, so without further ado, let's continue!

Imports

It can be useful to be able to split up a script into separate files. The extended syntax allows you to bring in functions and callbacks from such a script module by using the import keyword. The following sample script imports all functions from the file "MyFunctions.ksp", which is assumed to be placed in the same folder as the script importing it. This is equivalent to replacing the import statement with the contents of the given file. Importing can be used anywhere in your script, even inside callbacks or functions etc.

 SublimeKSP 
import "MyFunctions.txt"

It is also possible to import a module into its own namespace, like in the following example. All variables then need to be prefixed with the given namespace name, followed by the . operator (see families). Importing modules this way ensures there will be no variable name clashes with variables in the current script.

 SublimeKSP 
import "MyFunctions.ksp" as funcs

on init
    funcs.on_init
end on

on note
    funcs.humanization_factor := 40
    funcs.randomize_note_vel()
end on

The path specified after the import keyword can be one of the following:

  • absolute
  • relative
  • single file
  • folder (in which case all files with .ksp extension within that folder will be imported, in order)
  • URL (http:// or https://)

Pressing OptO (macOS) or AltO (Windows/Linux) when the text input cursor is focused on the line which imports a file will open that file in a new Sublime Text tab. In case the imported path is a folder, all .ksp files within that folder will be opened in a new Sublime Text window.

If you add the __IGNORE__ directive anywhere in a .ksp file, this file will be ignored when importing. This can come in handy when importing whole folders.

Pragma

It is possible to control how the compiler operates by using pragma directives. On the surface, it looks like a comment, but it is recognized as a special case by SublimeKSP. Pragmas must be formatted as follows: { #pragma <name> <value> }. These are the currently available pragmas and their usage:

<name> <value>
save_compiled_source "<file-path>"
preserve_names <variable-name>
compile_with <compiler-option>
compile_without <compiler-option>
Compiler Options
remove_whitespace
compact_variables
extra_syntax_checks
optimize_code 1
extra_branch_optimization
add_compile_date
combine_callbacks
sanitize_exit_command

1 If used on its own, this compiler option also force-enables extra_syntax_checks!

save_compiled_source instructs the compiler to save the compiled code to a file upon successful compilation. This is useful since it makes it easier to automatically update the script in Kontakt, which since version 4.2 has a feature that lets you link the source code to a certain text file. The path can be absolute or relative (note that in order to make relative paths work, the file has to be saved to disk beforehand!). It is possible to use multiple save_compiled_source pragmas in order to save the compiled code to multiple files simultaneously. Pressing OptO (macOS) or AltO (Windows/Linux) when the text input cursor is focused on a line with this pragma will open the target file in a new Sublime Text tab.

preserve_names instructs the compiler to exempt the specified variable from name obfuscation (e.g. if you want to use them with the various load/save array KSP functions and want the file names to be intelligible). This pragma only takes one variable as its argument, and variable type specifier is not required.

compile_with and compile_without forces the script to be compiled with a particular compiler option enabled or disabled. The way this works under the hood is as follows: the compiler is first scanning for all compile_with pragmas and collects them, then scans for all compile_without pragmas. However if the same compiler option is found twice, only the first match will be taken into account. Effectively, this means that compile_with pragmas take precedence over compile_without pragmas, and that one should be very careful not to define multiple pragmas for the same compiler option!

Comments

You can now use // to start a comment. Unlike the default {} comments, these comments always finish at the end of the line they were placed. {} comments still work as they used to. Sublime Text's line and block comment keyboard bindings will use the appropriate comment type, as well.

Note that you should not use // comments within inlined array initializers, since this is not supported by the transpiler! In this case, always use the standard{} comments.

There is also a new variant for block comment, /* */, like in C/C++, and for all vintage Pascal afficionados out there, you can also use (* *) style block comment, as well!

User-Defined Code Sections

This is an extension of the standard KSP block comment. If you type a line of code like this (mind the whitespace after opening and before closing parentheses!):

 SublimeKSP 
{{ THIS IS A CERTAIN CODE SECTION }}

SublimeKSP extension will recognize this as a user-defined code section and show it in Sublime Text's symbol list (CmdR on macOS or CtrlR on Windows/Linux) with the name you specified between the double squiggly parentheses.

Number Formats

The extended syntax offers the following additional ways to write numbers in hexadecimal and binary formats:

 SublimeKSP 
declare a
a := 0x77AAFF // assigns 77AAFF, or 7842559 decimal
a := 1100b    // LSB-right, assigns 12 decimal
a := b1100    // LSB-left, assigns 3 decimal

Optional Parentheses in Control Flow and Loop Statements

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

 SublimeKSP   KSP 
while x <= 10
    // ...
end while

if x = 1
    // ...
end if

select x
    // ...
end select
while (x <= 10)
    { ... }
end while

if (x = 1)
    { ... }
end if

select (x)
    { ... }
end select

for Loops

Standard KSP only supports while loops, but with the extended syntax you can also use for loops.

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

It is also possible to loop downwards and/or optionally use a certain step size.

 SublimeKSP   KSP 
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 standard KSP.

 SublimeKSP   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

Additionally, else keyword can now also be used in select statement, acting as a default case (like the default keyword in C/C++, for example). Note that for this to work as intended, else case needs to be the last one in the select statement!

 SublimeKSP   KSP 
select x
    case 0
        // ...
    case 1
        // ...
    else
        // ...
end select
select ($x)
    case 0
        { ... }
    case 1
        { ... }
    case 080000000h to 07FFFFFFh
        { ... }
end select

Variable Prefixes Are (Mostly) Optional

In standard KSP, it is mandatory to prefix variables with one of the following characters: $, %, ~, ?, @, ! in order to specify their type. With the extended syntax, this is not necessary for integer types ($, %). For real (~, ?) and string (@, !) types, prefix is mandatory only on the declaration line. Prefixes can be omitted completely once the variables have been declared (see the last line in the example below).

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

Less Constrained Variable Initialization

You can now always assign a value to a variable on the same line you declare it, whereas before you could only do this with constants and scalar variables initialized with literals or constants.

 SublimeKSP   KSP 
declare modID := find_mod(0, "lfo")
declare @string := "text"
declare $modID
$modID := find_mod(0, "lfo")
declare @string
@string := "text"

Variable Persistence Shorthands

There are three new keywords that can be used when declaring a variable as a handy way of setting the persistence.

The first is pers which is the same as writing make_persistent() in the next line. The second is read which is the same as writing make_persistent() and then read_persistent_var() in the following two lines. The third is instpers which is the same as writing make_instr_persistent() in the next line.

 SublimeKSP   KSP 
declare pers var
declare pers arr[10]
declare read ui_switch sw1
declare instpers selected_tab
declare $var
make_persistent($var)

declare %arr[10]
make_persistent(%arr)

declare ui_switch $sw1
make_persistent($sw1)
read_persistent_var($sw1)

declare $selected_tab
make_instr_persistent($selected_tab)

Single-quoted Strings

This is a fairly self-explanatory syntax extension. You can now specify a string by using single quotes ('') instead of double quotes (""). Note that you need to escape both single and double quote characters, if you want to use them in a single-quoted string!

 SublimeKSP 
declare @str := 'This is my string which also has a \"quote\"!'

F-Strings

This extended syntax was inspired by the similar functionality found in Python language. F-strings (formatted strings) allow for much more simplified string interpolation and concatenation, and they can also inject arbitrary code expressions in the same way. To specify an f-string, simply place an f in front of a single-quoted string. To inject arbitrary code, use the angle brackets <> around the expression:

SublimeKSP KSP
on init
    declare a := random(0, 100)

    message(f'The value of variable a is <a>!')
    message(f'Number I\'m thinking of right now is <random(-100, 100)> :)')
end on
on init
    declare $a
    $a := random(0,100)
    message("Hello World! The value of variable a is " & $a & "!")
    message("Number I'm thinking of right now is " & random(-100,100) & " :)")
end on

String Array Improvements

You can now declare and initialize a string array on the same line, like you would an integer or real array. If you initialize it with one string only, the whole array will contain that same string. If you specify empty strings in the inline initialization list, they will be omitted from the compiled code.

 SublimeKSP   KSP 
on init
    declare !textArray[2]   := ("string 1", "string 2") 
    declare !textArray2[15] := ("initial text")
    declare !textArray3[6]  := ("", "only", "", "odd", "", "indices")
end on
on init
    declare $i

    declare !textArray[2]
    !textArray[0] := "string 1"
    !textArray[1] := "string 2"

    declare !textArray2[15]

    $i := 0
    while ($i < num_elements(!textArray2))
        !textArray2[$i] := "initial text"
        inc($i)
    end while

    declare !textArray3[6]
    !textArray3[1] := "only"
    !textArray3[3] := "odd"
    !textArray3[5] := "indices"
end on

Automatic Array Size

When you declare an array and initialize its elements on the same line, it is now optional to include the array size. You can access the size of the array later in your code with arrayName.SIZE.

 SublimeKSP 
on init
    declare myArray[]  := (79, 34, 22, 58)
    declare !strings[] := ("foo", "bar")
    message(myArray.SIZE)    // message(4)
end on

Array Concatenation

There is a new function for concatenating any number of single dimension arrays into one. When used in a declare statement, you can declare an array with an open size (declare arr[]) and then assign it a value using the concat() function. The array size will be automatically calculated to equal the sum of the array sizes of the concatenated arrays. The concat() function can also be used anywhere else in your code, just make sure the array is large enough, otherwise you will get an error when attempting to apply the script in Kontakt. The concat() function can take one or more arguments, the order in which you list them is the order in which the array will contain the values. If you just have one argument in the concat function, it essentially makes one array equal to the other.

SublimeKSP
on init
    declare data1[]   := (20, 30, 40, 60)
    declare data2[]   := (40, 70, 50, 90)
    declare myArray[] := concat(data1, data2)

    declare pers ui_slider volumeSliders [2] (0, 100)
    declare pers ui_slider tuneSliders   [2] (0, 100)

    declare allSliderIDs[] := concat(volumeSliders, tuneSliders)

    declare const NUM_TABLE_COLUMNS := 100

    declare ui_table myTable[NUM_TABLE_COLUMNS] (2, 2, 1000)
    declare tableData[NUM_TABLE_COLUMNS]
end on

on ui_control(myTable)
    tableData := concat(myTable)
end on

Families

With standard KSP there is no good way to organize variables, so there tends to be a huge list of variable declarations in the on init callback, which makes it hard to know what is used where. With the extended syntax you can now declare variables which belong to the same category in a family, and then refer to them by using family.variable construct. This will make variable names somewhat longer, but makes it easier to quickly grasp what a variable is used for. In the compiled script, 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 array below, one has to use keyswitch.N instead of just N. It is also possible to nest families

 SublimeKSP   KSP 
on init
    family keyswitch
      declare const N := 10

      declare current
      declare keys[keyswitch.N]
    end family
end on

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

    declare %keyswitch__keys[$keyswitch__N]
end on

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

Constant Blocks

You can declare a set of integer constants in a block. This is similar to enums in C/C++, in a way. The value of the constants can optionally be automatic, whereby the first one has a value of 0 and each one declared afterwards is incremented. An integer array with the name of the constant block is also generated, though as usual if you do not use it, the compiler will remove it when Optimize Compiled Code compiler option is enabled.

SublimeKSP
on init
    {{ EXAMPLE 1 }}

    const colours
        WHITE := 0xFFFFFF
        BLACK := 0x000000
        RED   := 0xFF0000
        GREEN := 0x00FF00
        BLUE  := 0x0000FF
    end const

    message(colours.SIZE)    // How many elements are in colours.
    message(colours.BLACK)   // message(0)
    message(colours[0])      // message(16777215)

    {{ EXAMPLE 2 }}

    const types
        SEQUENCER    // Auto-generated 0
        LFO          // Auto-generated 1
        GRID         // Auto-generated 2
    end const

    declare read ui_slider slider(0, types.SIZE - 1)

    if slider = types.SEQUENCER
        message(types[slider])
    end if
end on

Inlined Functions

In addition to the natively supported user functions that standard KSP supports (we will refer to them as "native functions"), the extended syntax adds support for inlined functions. User-defined functions in standard KSP do not support arguments nor return values, and certain built-in functions like allow_group() cannot be used within them. Kontakt also has some restrictions on the order in which functions are declared, but SublimeKSP compiler will automatically reorder the function definitions in the compiled code.

Inlined functions are declared similarily to native functions. However, you can optionally add arguments and a return value. These functions are invoked using essentially the same syntax as for native functions, but without the call keyword. If the function has a return value, you can invoke it in any expression, albeit with one limitation: if the body of the function definition consists of more than one line, then that function can only be invoked as the single thing on the right hand side of an assignment, e.g. y := myfunc(4, x). A function with return value and no arguments needs to be invoked like this: y := myfunc(). The empty parentheses are needed for syntax colorion, in order to distinguish the function call from an ordinary variable reference.

Note that the very same function definition can be inlined in one place and called as if it were a native function in another place. By deciding whether to use the call keyword or not, you decide whether the function should be inlined or outright called. Of course, if the function uses the extended functionality like arguments and return value, and you attempt to call it as if it were a native function, you will get a compilation error.

SublimeKSP KSP
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)

    if ($velocity < 1)
        $velocity := 1
    end if

    if ($velocity > 127)
        $velocity := 127
    end if

  change_velo($EVENT_ID, $velocity)
end on

Let's compare the raw and compiled code. All invocations of inlined functions are replaced by the body of the function upon compilation. Note how the arguments are inserted into the compiled code: any occurence of value, min and max in the function body is replaced by the arguments velocity, 1 and 127 respectively. It is possible to call another function within the body of a function, but since the body of every function has to be inlined at some point, it is not allowed for a function to directly or indirectly call itself (recursion).

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 interfere with each other. Local variables are prefixed with an underscore upon compilation (see $_tmp in the example below). If you want a variable declared inside a function to be accessible outside of it, you can either declare it using global keyword, e.g. declare global x, or make sure the function name starts with on_init - all variables declared inside that function will implicitly be considered global!

It is also possible to be explicit and fully qualify variables declared in functions with local keyword, e.g. declare local foo. Local variables will also work in native functions.

 SublimeKSP   KSP 
on init
    declare x := 1
    declare y := 5

    swap(x, y)
end on

function swap(a, b)
    declare tmp := a

    a := b
    b := tmp
end function
on init
    declare $x := 1
    declare $y := 5
    declare $_tmp

    $_tmp := $x
    $x := $y
    $y := $_tmp
end on

Return value behaves just as if you had passed an extra argument and assigned a value to it inside the function. The name result below carries no special meaning - you can use any name you choose. The max() function consists of multiple lines and can only be used on the right hand side of an assignment: z := max(x, y), whereas the ˙square()˙ function (which is a single line) can be used anywhere, for example in an arithmetic expression passed as an argument to a built-in KSP function.

 SublimeKSP   KSP 
on init
    declare x := 1
    declare y := 5
    declare z

    z := max(x, y)
    message(1 + square(y))
end on

function max(a, b) -> result
    if a > b
        result := a
    else
        result := b
    end if
end function

function square(a) -> result
    result := a * a
end function
on init
    declare $x := 1
    declare $y := 5
    declare $z

    if ($x > $y)
        $z := $x
    else
        $z := $y
    end if

    message(1 + $y * $y)
end on

NOTES:

  • Local variables which end 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 is 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 on init callback, local and global variables end up declared at the point where the function was invoked from the first time (any later invocations have no effect as far as variable declarations are concerned) in the order in which the functions were invoked.
    • For functions which are invoked from callbacks other than on init (and only those), declarations of global variables are placed at the top, and local variables at the bottom of the init callback, in the order in which the corresponding functions were declared.

Task Functions

Task functions (taskfunc, for short) are based on a system created by Robert Villwock (a.k.a. Big Bob) called Task Control Module (TCM, for short), which has been integrated into SublimeKSP. This extended syntax relies on native functions under the hood. However, arguments you pass to a task function and local variables declared inside it are specific to the current callback. Note that if you use the local keyword, the variable declared will be local to the function, but NOT wait-safe! To declare wait-safe local variables, simply write declare foo, no need to use the local keyword. Please read the official TCM Guide for more information.

If your script invokes wait() inside a function, you are running the risk of function re-entrance in the context of another callback instance (i.e. the first callback is paused, another is executed and happens to call the same function). This can cause problems with the latter invocation incorrectly overriding variable values set and relied upon in the first callback (after the wait() some variables would have unexpectedly assumed different values). Note that local variables declared inside inlined functions are global variables under the hood with a name unique to the function in which it is declared, however they are still not impervious to this issue.

Taskfuncs solve this problem by allocating an array with 32768 entries (by default, this can be increased to 1000000 if you declare SET_CONDITION(TCM_LARGE) in your script) and divides this into a number of chunks where each chunk can be assigned to a callback instance. This memory area is then used as a stack where arguments to taskfuncs, return values and local variables are stored. Since each executed callback gets its own memory storage, this solves the problem with values getting overwritten when a function is reentered. Moreover, taskfuncs make passing parameters to and returning results from functions without needing to rely on inlined functions. Although inlined functions are suitable in many cases, their main issue is that invoking them many times can greatly increase the size of the compiled code (if a 100 line function is invoked 10 times, it will result in 1000 lines of compiled code).

In short, the benefits of taskfuncs/TCM are as follows:

  • Invoking complex functions multiple times does not increase the size of the compiled script, since the call keyword is used under the hood.
  • Taskfuncs are reentrant (if a function is interrupted by a wait(), a second execution of it will not override values that are needed after the wait() ends).
  • Even if the wait() function is not used, the script developer benefits from being able to use parameters and return values without the significant increase to the size of the compiled code that inlined functions can result in.

In order to use taskfuncs, you need to add tcm.init(<stack_depth>) to the on init callback, where <stack_depth> is the size of the per-callback stacks. You also need to replace wait() with tcm.wait() inside the taskfunc body in order to ensure proper reentrance. wait_ticks() and wait_async() also have their TCM variants, as well (which are, obviously, tcm.wait_ticks() and tcm.wait_async()). Invoking a task function is identical to invoking an inlined function (call keyword is not used), but do note that call keyword will be added by the compiler. Here is an example in action:

SublimeKSP KSP
on init
    // each callback is able to store up to 100 values at a time
    // (space used for arguments and local variables)
    tcm.init(100)

    declare x
end on

taskfunc get_random_value(min, max) -> result
    declare r

    r := random(min, max)

    tcm.wait(500000)

    // will use the right value of r even on function reentrance
    result := r
end taskfunc

on note
    // if another note is played, get_random_value() may be reentered
    x := get_random_value(10, 40)

    message('x = ' & x)
end on
on init
    { initialization of TCM }

    declare $x
end on

{ added by TCM }
function _twait()
    { ... }
end function

function get_random_value()
    %p[$sp - 5] := $fp
    $fp := $sp - 5
    $sp := $fp
    %p[$fp + 1] := random(%p[$fp + 2], %p[$fp + 3])
    %p[$sp - 1] := 500000

    call _twait()

    %p[$fp + 4] := %p[$fp + 1]
    $sp := $fp
    $fp := %p[$fp]
    $sp := $sp + 5
end function

on note
    %p[$sp - 3] := 10
    %p[$sp - 2] := 40

    call get_random_value()

    $x := %p[$sp - 1]

    message("x = " & $x)
end on

Note how the reference to the local variable r is changed into the stack reference %p[$fp + 1]. This highlights that a local variable of a taskfunc is stored in a place unique to each callback. A local variable of an inlined function on the other hand just gets a unique variable name, but uses global storage (hence the reentrancy problems in that case).

By default arguments are just passed into a function and any changes to their values don't make it out. If you want a parameter to be passed both into and out of a taskfunc, you can prefix it with the keyword var. If you want it to only be passed out (as a kind of result variable where the input doesn't matter), you can prefix it with the keyword out. An example follows:

 SublimeKSP 
taskfunc swap_get_max(var a, var b, out max)
    declare tmp := a

    a := b
    b := tmp

    if a > b
        max := a
    else
        max := b
    end if
end taskfunc

An invocation of swap_get_max() ends up compiled like this:

 SublimeKSP   KSP 
swap_get_max(x, y, z)
%p[$sp - 3] := $x
%p[$sp - 2] := $y

call swap_get_max()

$x := %p[$sp - 3]
$y := %p[$sp - 2]
$z := %p[$sp - 1]

Note how x and y are both passed in and out of the stack %p, whereas z is only passed out. Normally, you would use a return value declared using the -> result syntax instead of the out keyword like this. However, out is useful in case you want to return multiple values from a function.

Function Overriding

This syntax extension allows any function type (built-in KSP functions, native callable user functions, inlined functions, taskfuncs) can be overridden by using the override keyword. If you have two or more functions with the same name, the function with the override keyword will take priority. This keyword goes at the end of the function declaration line.

 SublimeKSP 
function foo()
    // this one won't be used
end function

function foo() override
    // this one will be used instead 
end function

on note
    call foo()
end on

Properties

Properties can be considered as sort of "pseudo-variables". When you use the name of the property inside of an expression, the property reference is replaced with an inline invocation of the get() function of the property. When you use the name of the property on the left hand side of an assignment, the set() function of the property is invoked inline, and the right hand side expression is passed as an argument to it.

 SublimeKSP   KSP 
on init
    property volume
        function get() -> result
            result := get_engine_par(ENGINE_PAR_VOLUME, 1, -1, -1)
        end function

        function set(value)
            set_engine_par(ENGINE_PAR_VOLUME, value, -1, -1, -1)
        end function
    end property

    // invokes get() function
    message('Volume: ' & volume)

    // invokes set() function
    volume := 500000
end on
on init
    message("Volume: " & get_engine_par($ENGINE_PAR_VOLUME, -1, -1, -1))
    set_engine_par($ENGINE_PAR_VOLUME, 500000, -1, -1, -1)
end on

You can also make properties that behave like array variables - even with more than one index. For example:

 SublimeKSP 
on init
    declare data[100]  // 10 rows, 10 columns

    property matrix
        function get(x, y) -> result
            result := data[x * 10 + y]
        end function

        function set(x, y, value)
            data[x * 10 + y] := value
        end function
    end property

    matrix[4, 5] := 10
    message(matrix[4, 5])
end on

If multiple indices are used, they are separated by a comma as in the example above. The indices are automatically paired up with the arguments of get() and set() functions from left to right. The last parameter of the set() function is always the value to be set. Note that it is technically possible to pass matrix[0] (the first column) as an actual argument to the property, and then within the function add a second reference to the row.

In some cases, readability can be enhanced by specifying the indices at separate places of a property name. For example, if the property in the example above had been named col.row, then instead of writing matrix[4, 5] one could write col[4].row[5]. The indices are moved to the end by the compiler, so it would be equivalent to writing col.row[4, 5].

Macros

Macros are in many ways similar to functions. However, while functions will interpret the code inside the function body, e.g. to support declaration of local variables, macros are used to simply perform text substitutions. Macros are executed at the beginning of the compilation process. The differences between functions and macros are:

  • A macro may not invoke other macros.
  • Macro arguments can be used more freely, e.g. in declare statements and as part of variable names (however, not inside strings).
  • A macro definition may contain top-level constructs like callbacks and function definitions, in which case the macro must be invoked at the top level (outside of callbacks/functions).
 SublimeKSP   KSP 
macro declare_button(#idx#, #name#)
    declare ui_button button#idx#

    set_text(button#idx#, #name#)
end macro

on init
    declare_button(0, "First")
    declare_button(1, "Second")
    declare_button(2, "Third")
end on
on init
  declare ui_button $button0
  set_text($button0, "First")
  declare ui_button $button1
  set_text($button1, "Second")
  declare ui_button $button2
  set_text($button2, "Third")
end on
 SublimeKSP   KSP 
macro on_uicb(#control#, #command#)
    on ui_control(#control#)
        #command#
    end on
end macro

on init
    declare ui_button active
end on

on_uicb(active, message("Value: " & active))
on init
    declare ui_button $active
end on

on ui_control($active)
    message("Value: " & $active)
end on

Macros can also be overloaded. This means that two or more macros that have the same name, but different bodies, can be executed based on the number of arguments they have.

 SublimeKSP   KSP 
macro mul(#a#, #b#)
    #a# * #b#
end macro

macro mul(#a#, #b#, #c#)
    #a# * #b# * #c#
end macro

macro mul(#a#, #b#, #c#, #d#)
    #a# * #b# * #c# * #d#
end macro

on init
    message(mul(2, 3))
    message(mul(4, 5, 6))
    message(mul(7, 8, 9, 10))
end on
on init
    message(6)
    message(120)
    message(5040)
end on

Define Macros

Define macros (or defines, for short) are a way to do text substitutions at the very beginning of the compilation process. They are completely global and can be declared anywhere. They are not a part of your actual script, so it is usally best to declare them outside of any callbacks (though sometimes it can improve readablity to put them inside callbacks or functions).

Some important things to note about defines:

  • Defines will substitute anything - variable names, built-in Kontakt functions, etc. Because of this, it is very important to use unique names for them. Using capital letters for the name is a good idea.
  • They are just simple text substitutions, they have no concept of the script's structure. If you create a define in a regular macro (or any location), it will still exist regardless of whether the macro is called or not.
  • Defines can be combined with USE_CODE_IF conditions to create advanced conditional behaviors, such as calling different functions or using different variables.
  • If you assign an arithmetic expression to a define, wrap it in brackets. Defines will try and evaluate any math expressions, however if the expression contains a declare const (for example), it will not be able to, therefore brackets are needed in order to ensure correct behavior.
  • Don't use variable type specifiers when naming defines!
 SublimeKSP   KSP 
define NUM := 20
define VALUE := (20 / 3)
define STRING := "text"
define FUNC(a, b) := (a + b)
define MENU_NAME(#name#) := #name#Menu

on init
    message(NUM)
    message(FUNC(1, 2) * FUNC(3, 4))
    declare ui_menu MENU_NAME(sound)
end on
on init
    message(20)
    message((1 + 2) * (3 + 4))
    declare ui_menu soundMenu
end on

Iterate Macros

Extended syntax for iterating a macro in a similar way to a for loop. This is primarily useful in situations where Kontakt forces you to use a UI variable name instead of a UI ID. First create a macro with one integer argument. Then use iterate_macro() command to execute the macro a given number of times. The number of times is set in the same way a 'for' loop works. Note that if you set the range for iterating the macro in an invalid way (for example, minimum value is larger than maximum value), the macro simply won't be iterated at all!

SublimeKSP
define NUM_MENUS := 5

on init
    declare pers ui_menu menus[NUM_MENUS]

    macro addMenuItems(#n#)
        add_menu_item(menus#n#, "Item1", 0)
    end macro

    iterate_macro(addMenuItems) := 0 to NUM_MENUS - 1
end on 

macro macroCallbacks(#n#)
    on ui_control(menus#n#)
        message(menus#n#)
    end on
end macro

iterate_macro(macroCallbacks) := 0 to NUM_MENUS - 1

There is a also short way of iterating a one line macro. Simply put the line you want to repeat as the argument of iterate_macro() command, and use token #n# to designate the places where you want numbers to be substituted. At least one such token must be found in order to use this functionality.

SublimeKSP
define NUM_MENUS := 20

on init
    declare ui_menu menus[NUM_MENUS]
    iterate_macro(add_menu_item(menus#n#, "Item", 0)) := 0 to NUM_MENUS - 1
end on

There is also iterate_post_macro(), which is a variant of iterate_macro() executed after the compiler expands all macros. This means that we can use macro arguments instead of define constants, allowing more flexible repeatable structures. For example:

 SublimeKSP 
iterate_post_macro(declare ui_button #n#) := #start# to #end#

Literate Macros

Extended syntax for iterating a macro with a list of literal strings instead of numbers. These literal strings are used by the function to call the macro multiple times, every time using a different string as the argument. In similar fashion to the iterate_macro() function, you first need to create a macro with one argument, but this time the argument is text replacement. The function expects a list of comma-separated strings - it is often useful to specify this list beforehand using a define. This function also has a shorthand for one line macros, this time using #l# as the token instead of #n#. However, #n# token can still be used in one line literate macros, in order to use the ordinal number of the literal as an argument.

SublimeKSP
define SLIDER_NAMES := volume, pan, tune

on init
    literate_macro(declare ui_slider #l#_slider (0, 100)) on SLIDER_NAMES
    literate_macro(message(#l#)) on "s1", "s2", "s3"
end on

macro sliderCallbacks(#name#)
    on ui_control(#name#_slider)
        message(#name#_slider)
    end on
end macro

literate_macro(sliderCallbacks) on SLIDER_NAMES

macro sliderCallbacks2(#arg1#, #arg2#)
    on ui_control(#arg1#_slider)
        message("Slider #arg1# has ID #arg2#")
    end on
end macro

literate_macro(sliderCallbacks2(#l#, #n#)) on SLIDER_NAMES

There is also literate_post_macro(), which is a variant of literate_macro() that is executed after the compiler expands all macros. This means that we can use macro arguments instead of define constants, allowing more flexible repeatable structures. For example:

 SublimeKSP 
literate_post_macro(declare #l#) on #obj#.CONTROLS

Literate macro has an additional helpful feature for appending or prepending literal entries to an existing define macro by using operators += for appending, and =+ for prepending. For example:

 SublimeKSP   KSP 
define FOO := age
define FOO += height
define FOO += weight

define FOO =+ name, surname

on init
    literate_macro(declare #l#) on FOO
    literate_macro(message(#l#)) on FOO
end on
on init
    declare $name
    declare $surname
    declare $age
    declare $height
    declare $weight
    message($name)
    message($surname)
    message($age)
    message($height)
    message($weight)
end on

Built-in Defines

Extended syntax contains built-in defines which allow you to use the compilation date and time in various formats directly in your code, as strings. They are:

SublimeKSP
__SEC__
__MIN__
__HOUR__
__HOUR12__
__AMPM__
__DAY__
__MONTH__
__YEAR__
__YEAR2__
__LOCALE_MONTH__
__LOCALE_MONTH_ABBR__
__LOCALE_DATE__
__LOCALE_TIME__

Automatic Number Incrementer

This is a built-in text replacement macro for incrementing a number over lines of code. Each new line the macro will be incremented by a specified amount. The syntax to start the incrementer is START_INC(text, startNum, stepNum). You can then end the incrementer with END_INC. The incrementer is substituted after normal macros are made, this makes it slightly more powerful than just a list of numbers in the source.

 SublimeKSP   KSP 
on init
    START_INC(N, 0, 1)
    message(N)     // message(0)
    message(N & N) // message(1 & 1)
    END_INC

    f()

    declare i
    declare list !menuItems[]

    START_INC(N, 0, 1)
    CreateMenuItem(LFO, "LFO")
    CreateMenuItem(ENV, "Envelope")
    CreateMenuItem(CC , "CC Num")
    END_INC

    declare ui_menu Mod

    for i := 0 to num_elements(menuItems) - 1
        add_menu_item(Mod, menuItems[i], i)
    end for
end on

function f()
    START_INC(N, -2, -1)
    message(N) // message(-2)
    message(N) // message(-3)
    END_INC
end function

macro CreateMenuItem(ID, text)
    declare const ID := N

    list_add(menuItems, text)
end macro
on init
    message(0)
    message(1 & 1)
    message(-2)
    message(-3)

    declare $i
    declare !menuItems[3]
    !menuItems[0] := "LFO"
    !menuItems[1] := "Envelope"
    !menuItems[2] := "CC Num"

    declare ui_menu $Mod

    $i := 0
    while ($i<num_elements(!menuItems))
        add_menu_item($Mod,!menuItems[$i],$i)
        inc($i)
    end while
end on

Multidimensional Arrays

This extended syntax is used to create arrays with multiple dimensions. These can be either integers, strings or UI widgets. They will work with variable persistence shorthands. Behind the scenes, these arrays work by declaring a regular array and a property. If you want to access the raw array, you need to prefix the name of the array with an underscore: _array[0]. Each array also has built-in constants for the number of elements in each dimension. They follow this pattern: <array-name>.SIZE_D1, <array-name>.SIZE_D2, etc. You can inline initialize a multidimensional array, this works in the same way as you would initialize a regular array, regardless of the number of dimensions.

SublimeKSP
on init
    declare array[2, 3] := (1, 4, 6, 3, 7, 7)   // 2D array with its 6 elements initialised.
    array[0, 1] := 100
    message(array[0, 1])
    message(array.SIZE_D2)
    load_array(_array, 1)                       // When referencing the whole (raw) array, prefix the name with underscore
    declare !text[2, 2, 2] := ("text")          // 3D array, all elements initialised to "text"
end on

UI Arrays

UI arrays are simply duplicates of the declared UI widget, which can be accessed through an array. Use square brackets to declare the number of elements. Native KSP constants cannot be used here, only defines or literal numerical values are valid. For ui_table and ui_xy widgets, the first square bracket is for the number of elements in the UI array, the second is for the array size of the widget. The UI ID of each widget can be accessed with arrayName[<idx>], or if you need the actual raw widget name, it's arrayName<idx>. UI arrays can also be multidimensional. In this case, to access the raw variable names or the single dimension version, you must prefix the name with an underscore. Note that there are no .SIZE constants generated for a multidimensional UI array. UI callbacks for a UI array can all be easily generated with a macro (see Iterate Macros section).

SublimeKSP
define NUM_CONTROLS := 40

on init
    declare pers ui_slider volumeSliders[NUM_CONTROLS] (0, 100)
    declare ui_table tables[40] [100](2, 4, 100)
    declare ui_switch switches[2, 2]

    _switches0 -> value := 1    // Access the variable name of a multi-dim UI array with an underscore
end on
SublimeKSP
define NUM_LAYERS := 4
define NUM_CONTROLS := 4

on init
    // Create all of the UI in one command. The total number of controls created is simply equal to NUM_LAYERS * NUM_CONTROLS
    declare pers ui_slider sliders[NUM_LAYERS, NUM_CONTROLS] (0, 100)
    declare i
    declare j

    for i := 0 to NUM_LAYERS - 1
        for j := 0 to NUM_CONTROLS - 1
            set_bounds(sliders[i, j], 10 + 40 * i, 10)
        end for
    end for
end on

function hideAllSliders()
    // Example of how you might want to use the single dimension variant to affect all sliders at once
    for i := 0 to num_elements(_sliders)
        _sliders[i] -> hide := HIDE_WHOLE_CONTROL
    end for
end function

macro sliderCallbacks(#n#)
    on ui_control(_sliders#n#)  // The single dimension variant has an underscore
        message(_sliders#n#)
    end on
end macro

iterate_macro(sliderCallbacks) := 0 to NUM_LAYERS * NUM_CONTROLS - 1 

Lists

Lists are a simple construct that allow you to append values to the end without having to specify the element index. Once declared, use the list_add() command to add values. They can only be used in the on init callback, however not within any loops or if statements. There is a constant for the size generated: listName.SIZE.

 SublimeKSP 
on init
    declare list controlIds[]
    list_add(controlIds, get_ui_id(slider0))
    list_add(controlIds, get_ui_id(slider1))
end on

Instead of using a whole lot of list_add() commands, you can declare a list and assign it a set of values in a block. There is also a constant variable for the list size generated. You cannot use variable persistence shorthands for the list block.

SublimeKSP
on init
    list !menuItemText[]
        "Oscillator"
        "Filter"  
        "LFO"
    end list

    message(menuItemText[0])
    message(menuItemText.SIZE)

    list myList[]
        99
        24
        get_ui_id(volSlider)
        get_ui_id(tuneSlider)
        get_ui_id(panSlider)
    end list
end on

You can also create a list of lists (also called jagged arrays). This is a 2D version of the list array type, in which you can add single dimension arrays (or regular variables) to the list as elements. The syntax is similar to regular lists, except you are required to leave open brackets with a comma in them, like so: [,]. You can then access the data in the regular 2D array fashion: arrayName[0, 2]. Like with regular multidimensional arrays, if you need to access the raw single dimension version, you can prefix the name with an underscore.

Because the size of each element can be different, it is important to be careful that you are accessing the correct elements. The size of each element is stored in a special array called arrayName.sizes. So, for example, to get the size of the first element, it would be arrayName.sizes[0]. The number of elements can be retrieved with the constant arrayName.SIZE. Note that this is different from num_elements(arrayName). Lists of lists also can be written in a list block - to denote that it is a list of lists, open brackets with a comma are required. If the data can be initialized on a declare array line (literals or constants), you can also just list raw data separated by commas in the list block.

SublimeKSP
on init
    declare filterParams [] := (ENGINE_PAR_CUTOFF, ENGINE_PAR_RESONANCE)
    declare reverbParams [] := (ENGINE_PAR_RV_SIZE, ENGINE_PAR_RV_DAMPING, ENGINE_PAR_RV_COLOUR)
    declare delayParams  [] := (ENGINE_PAR_DL_DAMPING, ENGINE_PAR_DL_TIME, ENGINE_PAR_DL_PAN, ENGINE_PAR_DL_FEEDBACK)
    declare wetParam        := ENGINE_PAR_SEND_EFFECT_OUTPUT_GAIN

    declare list engineParams[,]
    list_add(engineParams, filterParams)
    list_add(engineParams, reverbParams)
    list_add(engineParams, delayParams)
    list_add(engineParams, wetParam)

    message(engineParams.SIZE)
    message(engineParams[1, 1]) // Equal to ENGINE_PAR_RV_DAMPING.

    activate_logger("C:/LogFile.nka")
    PrintAllEngineParams()
end on

function PrintAllEngineParams()
    declare i
    declare j

    for i := 0 to engineParams.SIZE - 1
        for j := 0 to engineParams.sizes[i] - 1
            print(engineParams[i, j])
        end for
    end for
end function

{{ SHORTER VERSION OF THE ABOVE }}

on init
    list engineParamsShorter[,]
        filterParams
        reverbParams
        delayParams
        wetParam
    end list

    // Or even shorter, the values can be comma-separated on new lines
    list engineParamsEvenShorter[,]
        ENGINE_PAR_CUTOFF, ENGINE_PAR_RESONANCE
        ENGINE_PAR_RV_SIZE, ENGINE_PAR_RV_DAMPING, ENGINE_PAR_RV_COLOUR
        ENGINE_PAR_DL_DAMPING, ENGINE_PAR_DL_TIME, ENGINE_PAR_DL_PAN, ENGINE_PAR_DL_FEEDBACK
        ENGINE_PAR_SEND_EFFECT_OUTPUT_GAIN
    end list
on init

Structs

Structs are a data structure for variables. They are used to group together a set of similar variables. While not as fully featured as structs you might find in other programming languages, they are nonetheless useful. They can be considered to be instances of families, rather than fully fledged structures.

First, a struct is declared by using a block that starts with struct <name> and ends with end struct. It doesn't have to be declared inside on init callback. Inside this block, you can only put variable declarations - and nothing else. Once this struct is created, instances of it can be declared in your script like so: declare &<name> <instanceName>, where <name> is the name of the struct block, and <instanceName> is the name of the specific instance you want to create. The & symbol is required for the compiler to know that it's dealing with a data structure. Structs can also be declared as arrays or multidimensional arrays: declare &<name> <instanceName>[20]. Structs can also have other structs as members - simply declare another struct inside the struct block. Members of the struct are accessed with operator ., similar to families.

Notes:

  • Arrays or multidimensional arrays of structs will have .SIZE constants generated.
  • As explained in the Multidimensional Arrays and UI Arrays sections, the raw single dimension version of an array can be accessed by prefixing the name with an underscore. For arrays of structs, the underscore of any members that are multidimensional will have the underscore at the beginning of the whole name, not just that dot-separated section.
  • It is possible to declare an instance of a struct in a function.
SublimeKSP
struct MyStruct
    declare var := 2
    declare array[2]
    declare ui_switch switch
end struct

struct Point
    declare x
    declare y
end struct

// Use a define macro to simulate passing the struct as an argument to a function.
define POINT(#pointName#) := #pointName#.x, #pointName#.y
function offsetPoint(x, y, amount)
    x := x + amount
    y := y + amount
end function

on init
    declare &MyStruct myStruct
    declare &MyStruct singleArray[2]
    declare &MyStruct multiArray[2, 3]
    message(singleArray.SIZE)
    message(multiArray.SIZE_D1)
    message(multiArray.SIZE_D2)
    singleArray[0].var := 20
    multiArray[0, 1].array[0] := 2
    singleArray[0].switch -> value := 0

    declare &Point myPoint
    offsetPoint(POINT(myPoint), 20)
end on

// UI controls in one-dimensional structs can be normally accessed like so:
on ui_control(singleArray.switch0)
    message("switch from singleArray")
end on

// Because ui_control callback does not accept UI IDs, we must access the raw version of the switch by using the underscore.
// Note the position of the underscore - at the front of the whole name, not after the dot!
on ui_control(_multiArray.switch0)
    message("switch from multiArray")
end on

Limitations:

  • You cannot initialize a struct member with a function, e.g. declare var := find_zone("zone"). However these are fine: declare num := ENGINE_PAR_VOLUME or declare @text := "blah"
  • You cannot use the struct instance name in any operations, everything must be done by accessing the members. For example, you cannot try and assign one struct instance to another, each member will have to be assigned individually. You could create functions that add all struct members if you wanted to, though.
  • You cannot make a struct persistent, but this can be done individually on the members.
  • Array struct members cannot be inline initialized with a list, e.g. declare array[3] := (2, 3, 4) is not allowed. However, arrays can be initialised to a single value: declare array[3] := (2)
  • Lists as struct members will only work for single instances of structs (not arrays).
  • You cannot make arrays of structs which have native KSP constants (declare const).

Control And Event Parameter Shorthands

It is possible to use a shorthand syntax for getting and setting KSP control and event parameters. For example:

 SublimeKSP 
ui_or_ui_id -> param := value    // setting a value
message(ui_or_ui_id -> param)    // getting a value

param is the last part of the control or event parameter constant. For example, hide for $CONTROL_PAR_HIDE or value for $CONTROL_PAR_VALUE. ui_or_ui_id can either be an ID of the UI control, or its actual name.

For control parameters, there are several aliases available, as well. For $EVENT_PAR_0 ... $EVENT_PAR_3, there is an exception to the rule of taking the last part of the constant name - you have to use the PAR_ part!

SublimeKSP
foo -> x := value           // instead of foo -> pos_x := value
foo -> y := value           // instead of foo -> pos_y := value
foo -> default := value     // instead of foo -> default_value := value
message(foo -> min)         // instead of message(foo -> min_value)
message(foo -> max)         // instead of message(foo -> max_value)

event_id -> par_0 := value  // event_id -> 0 := value will NOT work!

UI Widget Control Parameter Setters

This extended syntax allows for tidier scripts by condensing setting of various UI widget control parameters to one line of code. These commands take an optional number of arguments, so that the compiled code doesn't get unnecessarily bloated. There is a command for each type of UI widget, they have been chosen to set the most commonly used properties of each widget. There is also a set_bounds(x, y, width, height) that works with any type of UI widget. Below is a list of all the commands and their arguments. The first argument is the widget you want to use, it can either be its literal name or its UI ID.

SublimeKSP
set_bounds(control, x, y, width, height)
set_slider_properties(slider, default, picture, mouse_behaviour)
set_button_properties(button, text, picture, text_alignment, font_type, textpos_y)
set_knob_properties(knob, text, default)
set_label_properties(label, text, picture, text_alignment, font_type, textpos_y)
set_level_meter_properties(level_meter, bg_color, off_color, on_color, overload_color)
set_menu_properties(menu, picture, font_type, text_alignment, textpos_y)
set_switch_properties(switch, text, picture, text_alignment, font_type, textpos_y)
set_table_properties(table, bar_color, zero_line_color)
set_text_edit_properties(text_edit, text, picture, text_alignment, font_type, textpos_y)
set_value_edit_properties(value_edit, text, font_type, textpos_y, show_arrows)
set_waveform_properties(waveform, bar_color, zero_line_color, bg_color, bg_alpha, wave_color, wave_cursor_color, slicemarkers_color, wf_vis_mode)
set_wavetable2d_properties(wavetable, wt_zone, bg_color, bg_alpha, wave_color, wave_alpha, wave_end_color, wave_end_alpha)
set_wavetable3d_properties(wavetable, wt_zone, bg_color, bg_alpha, wavetable_color, wavetable_alpha, wavetable_end_color, wavetable_end_alpha, parallax_x, parallax_y)
SublimeKSP
on init
    declare pers ui_switch onSwitch

    set_switch_properties(onSwitch, "", "")
    set_bounds(onSwitch, 0, 0, 20, 20)

    declare pers ui_slider volumeSliders[4](0, 1000000)

    declare i
    for i := 0 to num_elements(volumeSliders) - 1
        set_slider_properties(volumeSliders[i], 500000, "Knob")

        // We don't need to set width or height, so we can leave those arguments out
        set_bounds(volumeSliders[i], 10 + i * 50, 10)
    end for
end on

Creator Tools GUI Designer support

Native Instruments' Creator Tools has a property-based GUI Designer tool to create performance views (since version 1.2). There is a new file type to store these performance views, .nckp, which is JSON-based. SublimeKSP supports importing these files, so that the compiler can pull all UI control definitions from them and proceed with compilation smoothly.

This is achieved with a new command, import_nckp(<path>). This command can be used in any callback (or even outside of them). Path can be absolute or relative. Multiple .nckp files can be imported and parsed. Note that this only parses the files, it doesn't actually load the performance views. For this, you still need to use load_performance_view() command.

Compiler will throw an error if the .nckp file does not exist at the specified path, and also if both make_perfview and load_performance_view() are found inside the on init callback, as this is not supported by Kontakt.

SublimeKSP
on init
	import_nckp("C:\Work\Project\Resources\performance_view\my_GUI.nckp")
	load_performance_view("my_GUI")
end on

SublimeKSP Options

There are a number of actions and options which govern various aspects of the plugin and compiler available. They are found in Tools > SublimeKSP menu, which will be displayed only when the syntax of the document is set to KSP. You can also use Sublime Text's Command Palette and search for KSP. In this section, we will explain all of these.

Compile

Very self-explanatory. This action will compile the currently focused document in Sublime Text. After compilation, the source code will be available in the operating system's clipboard. Additionally, source code can be piped to one or more files using the save_compiled_source pragma directive. By default, this action is bound to CmdK (macOS) and F5 on Windows/Linux.

You can monitor the compilation progress in Sublime Text's status bar, or the console which you can open via View > Show Console, or by pressing Cmd` (macOS) or Ctrl>` (Windows/Linux).

Compile All Open Scripts WIth Defined Output Path

A more advanced version of the Compile command, this action will scan through all opened documents in the currently active Sublime Text window, look for any documents that have the save_compiled_source pragma directive set, and compile them in order they were found. By default, this action is bound to CmdOptK (macOS) or ShiftF5 (Windows/Linux).

Recompile Last Script

Almost the same as the Compile command, with the difference that you don't have to be focused on the document you want to compile. You could be looking at a different file, but the one that you compiled last will be compiled again. By default, this action is bound to CmdShiftK (macOS) or CtrlF5 (Windows/Linux).

Uncompress Selected Compiled Code

In case the script has been compiled with Compact Variables option enabled, you can quickly reverse this by selecting a portion of the compiled code, then executing this action, in order to replace the obfuscated variable names with non-obfuscated, readable ones. The caveat here is that you can only do this after an executed compilation - you cannot simply take any obfuscated script out there and deobfuscate it like this, because variable rename map of the last successful compilation is required for this process to work.

Play Sound When Compilation Finishes

Another self-explanatory option. Enabling this option will play a short plucky sound if the compilation was completed successfully. You can replace this sound with your own by following these steps:

  1. Open the command pallete in Sublime Text (CmdShiftP on macOS or CtrlShiftP on Windows/Linux)
  2. Search for "Package Control: List Unmanaged Packages" command
  3. Type KSP to find "KSP (Kontakt Script Processor)" in the list
  4. Press Enter. This will open your operating system's file explorer focused on the folder where SublimeKSP
  5. In this folder you should see a subfolder called "sounds". Open it and replace "finished.wav" with an audio file of your choice (WAV and AIFF formats should work)

Remove Indents

Yet another self-explanatory option. Enabling this option will produce compiled code without any indentation. Empty lines are always removed during compilation, as a consequence of creating the AST.

Compact Variables

Enabling this option will obfuscate all variable names to a 5-character SHA1 hash based on their original names. This will considerably reduce the file size of the compiled script, which can result in faster script applying/parsing on Kontakt's end. However, debugging obfuscated scripts becomes a near impossibility as a consequence. Choose your destiny, as they say!

Extra Syntax Checks

Enabling this option will execute additional syntax checks during compilation: expression types, statement types, declarations, and raise errors if they are found. It is recommended to enable this option at all times. It may increase compilation time with larger scripts, though.

Optimize Compiled Code

Enabling this option is not possible unless Extra Syntax Checks option is enabled first. Once enabled, the compiler will be instructed to simplify expressions as much as possible (for example any math operations involving constants and/or literals will be calculated and the result will be used in the compiled result, instead of the whole expression), to find and remove unused functions, and to find and remove unused variables. In a lot of cases having this option enabled can significantly reduce the size of the compiled script.

Additional Branch Optimization

Enabling this option is not possible unless Extra Syntax Checks option is enabled first. Once enabled, the compiler will be instructed to run an additional optimization of branching statements executed before checking variable declarations, which allows using if-else with defines. Example:

SublimeKSP
define TEST := 5

on init
   if TEST > 7
        declare some_var
   end if

   message(some_var)
end on

This code will produce an error because the condition is false.

Indent Size In Compiled Code

This option specifies how many spaces an indent in compiled code will be. Default value is 2. Note that this option is only taken into account if Remove Indents option is disabled!

Add Compilation Date/Time Command

Enabling this option will add a comment atop the compiled code which contains the compilation date and time. Easy!

Combine Duplicate Callbacks

Enabling this option will look for duplicate callback declarations (which can sometimes happen when importing source code from other files), and merge them into a single callback, in order they were found. Example:

SublimeKSP KSP
on note
    play_note(EVENT_NOTE + 12, EVENT_VELOCITY, 0, -1)
end on

on note
    change_tune(EVENT_ID, -120000, 0)
end on
on note
    play_note($EVENT_NOTE + 12, $EVENT_VELOCITY, 0, -1)
    change_tune($EVENT_ID, -120000, 0)
end on

Sanitize Behavior of exit Command

This option enables a workaround for a bug in behavior of exit KSP command that existed until Kontakt 7.5. If you have a function which uses this command at the beginning of its body, calling it would collapse the entire function call stack, instead of only existing just the called function (as per description in KSP reference). The workaround inserts a dummy no-op statement before exit, which prevents the function call stack collapse.

If you are developing for Kontakt versions before 7.5, absolutely make sure this option is enabled, otherwise you can disable it.

Enable Completions for Vanilla KSP Built-ins

Modifying this option requires a full restart of Sublime Text. Once enabled, SublimeKSP plugin will offer additional syntax completions for built-in KSP constants and variables which contain the type specifier ($, %, ~, ?, @, !). By default, only the shortened completions are available.

SublimeKSP Documentation

Executing this action will open your default Web browser (if it's not already running) and open this Wiki.

About SublimeKSP

Executing this action will open your default Web browser (if it's not already running) and open the README file of this repository.