-
Notifications
You must be signed in to change notification settings - Fork 18
Home
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.
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.
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!
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.
|
---|
|
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.
|
---|
|
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://
orhttps://
)
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.
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!
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!
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 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.
The extended syntax offers the following additional ways to write numbers in hexadecimal and binary formats:
|
---|
|
In an effort to make the source code slightly more readable, parentheses are optional for if
and select
statements and loops.
|
|
---|---|
|
|
Standard KSP only supports while
loops, but with the extended syntax you can also use for
loops.
|
|
---|---|
|
|
It is also possible to loop downwards and/or optionally use a certain step size.
|
|
---|---|
|
|
The extended syntax provides an else if
construct, since this is lacking in standard KSP.
|
|
---|---|
|
|
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!
|
|
---|---|
|
|
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).
|
|
---|---|
|
|
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.
|
|
---|---|
|
|
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.
|
|
---|---|
|
|
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!
|
---|
|
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 |
---|---|
|
|
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.
|
|
---|---|
|
|
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
.
|
---|
|
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 |
---|
|
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
|
|
---|---|
|
|
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 |
---|
|
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 |
---|---|
|
|
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.
|
|
---|---|
|
|
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.
|
|
---|---|
|
|
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.
- For functions which are invoked directly or indirectly from the
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 thewait()
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 |
---|---|
|
|
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:
|
---|
|
An invocation of swap_get_max()
ends up compiled like this:
|
|
---|---|
|
|
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.
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.
|
---|
|
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.
|
|
---|---|
|
|
You can also make properties that behave like array variables - even with more than one index. For example:
|
---|
|
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 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).
|
|
---|---|
|
|
|
|
---|---|
|
|
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.
|
|
---|---|
|
|
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!
|
|
---|---|
|
|
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 |
---|
|
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 |
---|
|
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:
|
---|
|
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 |
---|
|
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:
|
---|
|
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:
|
|
---|---|
|
|
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 |
---|
|
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.
|
|
---|---|
|
|
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 |
---|
|
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 |
---|
|
SublimeKSP |
---|
|
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
.
|
---|
|
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 |
---|
|
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 |
---|
|
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 |
---|
|
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
ordeclare @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
).
It is possible to use a shorthand syntax for getting and setting KSP control and event parameters. For example:
|
---|
|
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 |
---|
|
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 |
---|
|
SublimeKSP |
---|
|
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 |
---|
|
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.
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).
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).
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).
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.
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:
- Open the command pallete in Sublime Text (CmdShiftP on macOS or CtrlShiftP on Windows/Linux)
- Search for "Package Control: List Unmanaged Packages" command
- Type
KSP
to find "KSP (Kontakt Script Processor)" in the list - Press Enter. This will open your operating system's file explorer focused on the folder where SublimeKSP
- 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)
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.
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!
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.
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.
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 |
---|
|
This code will produce an error because the condition is false.
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!
Enabling this option will add a comment atop the compiled code which contains the compilation date and time. Easy!
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 |
---|---|
|
|
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.
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.
Executing this action will open your default Web browser (if it's not already running) and open this Wiki.
Executing this action will open your default Web browser (if it's not already running) and open the README file of this repository.