Contents

What are scripts?

Scripts can be seen as small plugins. They are quite similar to the MIDI effect plugins you can use in sequencers but differs by being integrated into the sampler and this provides some benefits. Unlike MIDI effects, scripts have access to some internal Kontakt features which in addition lets them:

Scripts are written in the Kontakt Script Language and entered as plain text. A script basically consists of a number text lines with instructions to the Kontakt Script Processor (KSP). The KSP interpretes your script and executes your instructions when it receives notes or MIDI CC.

To be able to enter your own scripts you must learn the syntax of the Kontakt Script Language which is what this tutorial aims to help you with.

Managing scripts in Kontakt

To create and edit your own script in Kontakt you go through these steps (see image below):

  1. Make sure the Script Editor is active. If not activate it.
  2. Press the "Edit" button to show the text editor where the script is entered
  3. Specify a title for your script. Make sure to press the enter key after having typed this in or Kontakt will not remember the title properly. The title is completely up to you, choose something descriptive.
  4. Type in your script in the script text editor (the rest of the tutorial will show you how to write the scripts).
    It's possible to use a couple of shortcuts in the editor:
    Ctrl+A- select all
    Ctrl+X- cut
    Ctrl+C- copy
    Ctrl+V- paste

    Important: if you're accustomed to using Ctrl+Z for undo be sure to avoid it in the script editor as it'll discard your changes and you may loose all of what you have typed in.

  5. Press the "Apply" button for the changes to take effect. The little box to the left of the Apply button will light up whenever there are unapplied changes.

Managing scripts in Kontakt2

The process of writing a script consists of first going through steps 1-5 and then repeating steps 4-5 (editing and applying changes) until the script works as it should. A quick way to test any of the example scripts below is to copy it from this page, and then press Ctrl+A followed by Ctrl+V in the Kontakt Script Editor to replace any previous text with the script you want to try out, followed by clicking the Apply button.

Editing scripts

To make Kontakt load a script, the script code must be present in the internal Kontakt Script Editor and you have to press Apply to make any changes apply. However, there's nothing that says that you must actually enter the script text, henceforth called source code, using the internal script editor. You may equally well use any text editor of your choice to enter the source code, and then copy and paste it into the Kontakt script editor (followed by clicking Apply).

So why not use internal Kontakt script editor from start? Well, the reasons may vary: you may think the text size is too small, you want to use the undo function of your text editor, want an editor that does automatic indentation and code coloring etc. For really short scripts it's probably not worth the extra work of copying and pasting from an external editor, but for larger scripts I would like to recommend using my freely available KScript Editor (available both for Mac OSX and Windows).

Building blocks of problem solving

To perform a task you need to:

Just think about an everyday task such as sorting socks by color after laundry and you should be able to relate to all those parts (please actually try to picture this as vividly as you can). Scripting is basically no different, only now you're handling notes and you have to learn how to express to KSP what things needs to be remembered and what operations need to be performed, which is what the following sections is about. The main difference between instructing a human to do something and instructing a computer, is that the computer needs to have it specified in an unambigious language and broken down into small steps.

Callbacks

Scripts have four main parts called callbacks. A callback represents a task that is to be performed when some event occurs (eg. a note is pressed or the script is loaded). The word callback indicates that it's not up to you to decide when these tasks are performed, instead KSP triggers them for you (for a real world analogy think of someone calling you back on the phone letting you know something has happened, prompting you to do something). But it's still your task to say what should happen. Eg. when a note is pressed KSP triggers the note callback where you have specified what then should happen. The following script illustrates how these four parts are written:

on init
end on

on note
end on

on release
end on

on controller
end on

The "on init" and "end on" lines serves as markers of the beginning and end of the init callback. Between these you can add lines that specify what should happen in that callback, and the same goes for the three other callbacks. Since the callbacks above are empty (no such "middle" lines have been added) the script will do nothing and hence have no effect on incoming notes (it is a valid script however). In the next couple of sections you'll see how to make them actually do something but first a description of the four callbacks:

on init
The init callback is performed once at the time the script is loaded. This is a place where scripts do all kinds of initialization, eg. of user interface controls like knobs and edit boxes.
on note
The note callback is performed whenever a note is pressed. This gives your script a chance to alter the note (eg. pitch, velocity, volume, panning, tuning), or generate new notes (eg. for accompaniment).
on release
The release callback is performed whenever a note is released. This lets you customize which release samples are used, which could be useful eg. if your script is for a guitar instrument.
on controller
The controller callback is performed whenever a MIDI CC message is received. You can use it eg. to let your script react to movements of the mod-wheel or the sustain pedal being pressed or released.

Note that empty callbacks can be omitted if you like, and we will do that later. Furthermore I might add that actually there is one additional type of callback - a user interface callback which is performed whenever the user changes the setting of a certain knob or button. It'll be introduced later though.

Variables

Variables are the way for scripts to remember things - they are used for storing and retrieving information. So what is it that we need to remember? Well, it might be settings (what user control knobs or edit boxes are set to), or it might be information used to keep track of where we are in a process so that we know what to do next.

Script variables look just like the variables you're used to from algebra, except they start with the '$' character. So instead of eg. x and y we have $x and $y. The main difference between these variables and the ones you use when solving equations is that the script variables actually do vary - whereas the equation variables commonly have a fixed value that you're trying to find out. So one and the same script variable $x can contain different values at different points in time. The kind of variables shown here can contain a single integer value. A good way to think of them is as named containers of information - the name $x is used to refer to whatever number that variable contains (which depends on which number was previously stored in it).

To use a variable you must first declare it, which is a way of saying to Kontakt:
"I want an information container with this name. Please create it and let me use it to store/retrieve values henceforth"
.
All variables must be declared in the init callback part of your script and are then available for use in all callbacks.
Here's an example of a declaration:

on init
  declare $x
end on

To assign a value to a variable ("store the value in the container") you write $x := value where value can be anything from a simple number to a complex mathematical expression possibly containing other variables. When you assign a value to a variable the previous value it containted is lost and replaced with the new one. Here's an example of three variables being declared and then having values assigned to them. If nothing other is specified the lines of a callback are always performed sequentially by KSP, starting with the top line. If no value is assigned to a variable it will by default equal zero, until you change it. The script below will initialize $sum to the value 60.

on init
  declare $x
  declare $y
  declare $sum 
  $x := 40
  $y := 20
  $sum := $x + $y
end on

It's often the case that one wants to assign an initial value to a newly declared variable, and therefore it is also possible to combine declaration and assignment as this script shows.

on init
  declare $x := 40
  declare $y := 20  
end on

At some point you'll probably be confronted with code that looks like this:

$sum := $sum + $x

The two occurences of the variable on both sides of the assignment operator := may seem confusing, but then it's important to know that the right hand side is evaluated first. So first $sum + $x is calculated and then the resulting value is assigned to $sum (replacing its old value). So the line above actually is the way you would tell KSP to increase $sum by $x. If before running this line $sum was equal to 10 and $x was equal to 5, afterwards $sum will be 15 (and naturally $x will be unchanged).

Mathematical expressions

As the examples above showed you can combine numbers and values into mathematical expressions. Almost anywhere where one can use a number it's also possible to use more complex mathematical expressions, mixing variables and numbers as you wish. Here are a couple of examples:

When KSP executes one of your script lines it will start by evaluating all expressions it contains. For every variable in the expression the current value of the variable will be used. You can picture this as if every variable would have been substituted by the value it contains at the time the expresion is evaluated. Note that changing the value of a variable can make one and same expression evaluate to different values at different times.

Now you probably wonder what "mod" on the last line does. Just like +, -, / and * it is a mathematical operator. It returns the remainder resulting from dividing the two numbers, so 11 mod 4 will be evaluated to 3. Speaking of division also note that since Kontakt scripts only deal with integers the result of a division is rounded down to the nearest integer, eg. the result of 11 / 4 is 2 (2.75 rounded down). Because of this rounding it's important to think about in which order you do calculations, eg. say you want to calculate two thirds of the $x. Then you might be tempted to write 2/3 * $x, but 2/3 is rounded down to zero so the result will always be zero. Instead one should collect factors and try to do one single division as the last step like this: 2*$x / 3. The key thing is to try to keep precision as long as possible.

Array variables

In addition to normal variables introduced above it's also possible to use list variables. Instead of just containing one value these variables contains a fixed number of values which each can be accessed by specifying its position in the list. Each such value slot is denoted as element. The list variables themselves are called arrays. Say for example that you like to make a script that tunes the twelve different tones separately. Then you could declare an array variable of length 12 whose twelve elements would represent the different tunings. To distinguish array variables from normal variables they are prefixed with % instead of $. Here's an example:

on init
  declare %note_tunings[12]
end on

The size of the array is specified within brackets to the right of the name. Arrays in Kontakt scripts are always created with a fixed size that cannot be changed afterwards.

To access a specific element you write the variable name followed by the element index within brackets:

on init
  declare %note_tunings[12]
  %note_tunings[0] := 10
  %note_tunings[1] := -3
  %note_tunings[2] := -15
  %note_tunings[3] := %note_tunings[2] - 10
end on

The above example assigns values to the first four elements. Array elements are initialized to zero by default, so the elements not assigned to above will contain the value zero. An array element (written as the array name followed by the element index inside brackets) behaves in every way just like a normal variable. As you see the indexing does not start counting at 1 but instead at 0 - the first element in an array always has index zero. If this seems strange to you, think of the index as a distance from the beginning. The first element begins the array and therefor has distance 0, the second element lies at offset 1 from the beginning, the third at 2 and so on.

Note that the value inside brackets on the declaration line has a different meaning than values inside brackets on other lines. On the declaration the value signifies array size, and on the other lines it signifies element index. Finally we shall note that like the declaration and first assignment of a normal variable may be combined into one line, that is also the case for array variables. But an array is not initialized with a single value, but rather a list of values. This list of values is written inside parenthesis and the values comma-separated, like this:

on init
  declare %note_tunings[12] := (10, -3, -15, 5, 4, -8, 6, 3, -22, 0, -2, 7)
end on

This is just a shorter way of writing assignments to the individual elements like above. Naturally the number of values specified must be equal to the size of the array.

Constant variables

Sometimes it's desirable to prevent a variable's value from being changed after the declaration - to make the variable constant. To do this you use the word const in the declaration:

on init
  declare const $max_velocity := 100
end on

This variable behaves like any other with the exception that it's not possible to assign a new value to it (trying to do this will generate an error message). After having declared the constant you can use the variable name anywhere you would have written 100. So why do this instead of actually writing the value 100 which seems a lot easier? Well, using a constant variable instead has two benefits:

User interface controls

Scripts can use different types of user interface controls to let the end-user specify settings:

A user interface control can be seen as a special kind of variable whose value is not only used internally in the script but also displayed visually. And it turns out that the syntax for declaring user interface controls is very similar to how ordinary variables are declared. Let's create a button. Please try this script out in Kontakt and you'll see a button with the caption "mybutton" appear:

on init
  declare ui_button $mybutton
end on

The additional word "ui_button" in the declaration indicates that the variable $mybutton should appear visually as a button. The variable $mybutton will have the value 1 when the button is active/pressed and 0 otherwise. You can also change whether it's active/pressed or not by assigning either 0 or 1 to $mybutton, so it works in both directions. We will see later how the value of variables can be used to control which operations should be performed in the callbacks using conditional statements. For now let's look at knobs and value edits as well:

on init
  declare ui_knob $volume(-10, 10, 1)
  declare ui_value_edit $humanize_delay(0, 1000, 1000)
end on

As you see these are similar to the button but instead you use "ui_knob" and "ui_value_edit" in the declaration to show how they should be displayed. There's another difference: the parenthesis and the three comma-separated values to the right. These three values are mandatory and represent the minimum value, maximum value and scaling factor respectively. Min and max value is easy to understand; test the script above and you'll see that value range of volume knob is -10 to 10.

But what is the scaling factor? Well since script variables can only represent integer numbers it's necessary to work with small units to get good resolution. Eg. for a variable containing a delay setting, instead of measuring it in seconds we use milliseconds or microseconds - if we had used seconds it would only be possible to have whole seconds, not fractions like 0.1s since we only have integers at our disposal. Eg. the $humanize_delay variable above ranges from 0 to 1000 milliseconds. However, even though variable values can only be integers it is still possible to have Kontakt present the value in another unit to the user. That's what the scale factor is used for, the value presented to the user will be divided by the scale factor. So in the example above, the internally used milliseconds will be divided by one thousand when presented to the user who will see it as a setting between 0.000 and 1.000 seconds. The scaling only affects the presentation and setting it to 1 the internally used value will be presented as is to the user (since division by 1 leaves any number unchanged).

Knobs and value edit variables work similarily to the button. That is, you can control the knob/value edit by assigning a value to the variable anywhere in your script, and any change the user makes to the control will change the value.

Comments

As scripts grow larger it's good practice to annotate them with comments. Comments are text annotations that are completely ignored by KSP (the script processor) when executing the script. They have no effect whatsoever, but they provide a way for you to document and explain to readers of the script what you are doing. Think of commenting as writing a note in the margin of a book - while not changing the contents of the book they give you information about how to interprete the text.

Comments are to the benefit of other people who might be interested in your script, but mostly for your own sake. Without them you most likely will have forgotten your line of thought when you return to your own script after a couple of days/weeks of not working with it. Use of comments is not a sign of amateurishness, on the contrary they are often used extensively by experienced script writers. I chose to introduce the concept of comments here because our scripts will soon become more complex and we will need them to describe what we're doing. Here's an example of how to use them:

on init
  { volume adjustment (in dB) }
  declare ui_knob $volume(-10, 10, 1)
  
  { random delay for humanizing (in milliseconds) }
  declare ui_value_edit $humanize_delay(0, 1000, 1000)
end on

Anything within curley braces - { } - is considered a comment and ignored by KSP. You can place them anywhere in the code, eg. on their own lines as above or at the end of lines to make the script more compact:

on init  
  declare ui_knob $volume(-10, 10, 1)                   { volume adjustment (in dB)           }
  declare ui_value_edit $humanize_delay(0, 1000, 1000)  { humanizing delay  (in milliseconds) }
end on

Finally a note about whitespace. As you may have noticed in the first script above you can use empty lines wherever you wish to make scripts easier to read - just like you would divide a text into paragraphs. Furthermore on a single line, at any place where I used a single space character, you are free to use any number of spaces or tabs, eg. to align things (at many places you still need at least one white-space character though, to separate parts from each other). As an example of this, the two leading spaces used to indent the middle lines above are purely optional. However indentation is often used to put emphasis on the structure of the script - the indentation makes it easier to spot where the init callback begins and where it ends. Like comments extra whitespace only serves to make the code more comprehensible to humans without affecting how the script works.

Functions

So far I have shown how to declare variables and assign them values. To actually control the behaviour of Kontakt (which is the whole point of using a script) you need functions. Think of a function as a named operation/command that Kontakt can perform for you whenever your order it to. There are functions for a lot of tasks: fading in/out, playing notes, releasing notes, changing tuning, etc. Some functions need you to provide information (called parameters) that controls how the task is performed, and some functions also give you back an integer as result. Let's begin with looking at how you can let the script generate an artifical MIDI note. The script language provides a function called play_note for this purpose. This function requires of you to provide four pieces of information, or parameters:

note number (pitch)
a value in the 0-127 range as specified by the MIDI standard.
note velocity
a value in the 0-127 range as specified by the MIDI standard.
sample offset
for instruments which use Sampler mode this value may be used to start playback somewhere in the middle of the sample. To start playback from the start of the sample use the value 0. In DFD mode only 0 is supported.
note duration
the length of the note in microseconds. After this time a note-off message will automatically be generated.

To use a function you write it's name followed by any parameters (four in this case) wrapped inside parenthesis separated by comma:

on note
  play_note(60, 100, 0, 1000000)   { play C3 at velocity 100, release after 1 second }
end on

Please try this script in Kontakt. You will find that any note you play has an accompaniment of C3 (having note number 60), played at velocity 100, starting from the beginning of the sample, with a length of one second (one million microseconds). As I said earlier, whatever is written inside the note callback will be performed whenever a note is pressed. By the way, people use many different words to describe that a function is executed which can be good to know, eg. one say the function is called/invoked/executed/run. Also note, that while we passed simple numbers as parameters to the function above these could also have been mathematical expressions of arbitrary complexity. As an exercise, try changing some of the four parameters above and notice the different result as you play some notes on your keyboard.

In many cases you will want the length of your artificially generated notes to be the same as the note your pressed on your keyboard and which triggered the callback - you want the generated note to be released at the same time as the triggering note. As this is such a common use the play_note function provides a way to do this: just specify -1 for the note duration and Kontakt will handle the rest. Of course -1 is not used as the actual note duration but rather serves as a signal that the note duration should be handled in a special way.

on note
  play_note(60, 100, 0, -1)    { play C3 at velocity 100, release when the triggering note is }
end on

A final note about which notes trigger the note callback. If the artificially generated note above would trigger the note callback just like "real" notes do, using play_note would cause the lines inside the callback to be executed and another note would be played (that is what the above callback does, right) and that note would in turn trigger the note callback again, and we would have an infinite chain reaction. This is of course not desirable and is why KSP is designed so that only "real" notes (of external origin) trigger the note callback and not artificially generated ones.

Using functions and variables together

Let's now try to bring the two concepts of functions and variables together to make the above example less static:

on init
  declare ui_knob $note(0, 127, 1)       { declare knob to control which note to play     }
  declare ui_knob $velocity(0, 127, 1)   { declare knob to control velocity               }
  $note := 60                            { assign the value 60 to the first knob          }
  $velocity := 100                       { assign the value 100 to the second knob        }
end on

on note
  play_note($note, $velocity, 0, -1)     { play the user specified note at given velocity }
end on

As you see this script defines both what should happen in the init callback and in the note callback. In the init callback two variables displayed as knobs are declared. Their value range is 0-127 and the values displayed to the user are not rescaled (displayed as is) since the rescaling factor is 1. The two variables are initialized to the values 60 and 100 respectively. These are only initial values - if the user sets the knobs to other values later these two variables will then contain the changed values. As I said earlier the init callback is triggered only once when the script is loaded at which time its body, the four lines in this case, is executed. A script is considered to be loaded not only when you load a script from the script preset menu in Kontakt, but also every time you press the Apply button (causing the updated version of the script to load!).

In the note callback instead of using constant numbers as we previously did, now we pass variables as parameters to the function. As mentioned earlier you can picture this as if each time the line is executed, all variables would be replaced with whatever value they happen to contain at the time. As you see, the use of variables can make a single script line have different effects at different times depending on the values of the variables. If you turn the knobs, the values of the two variables will change and thereby the accompaniment note and its velocity (try this!).

Built-in variables

There are a number of built-in variables that provide you with information about things like tempo, time, MIDI controller states, etc. For example, in the note callback you will often want to know the pitch and velocity of the note that caused the triggering of the callback (the nonartificial note played on your keyboard). Most of the built-in variables can be used within all callbacks, but some of them contain information about the press/release of a note and may therefor only be used in the note and release callbacks (the only places where their use makes sense).

Now you may wonder what makes builtin-in variables different from the variables we used earlier. They are different by being:

Here are the most important built-in variables:

$EVENT_NOTE
the MIDI note number (pitch) of the note that triggered the note/release callback. In the range 0-127.
$EVENT_VELOCITY
the velocity of the note that triggered the note/release callback. In the range 0-127.
$EVENT_ID
a number which is unique to the note event that triggered the note/release callback. We'll use this later.

Every time a note or release callback is triggered KSP will assign suitable values to the above variables before executing the script code in the callback. If you play C3 on your keyboard $EVENT_NOTE will contain the value (note number) 60, and if you play C#3 the next second $EVENT_NOTE will then contain the value 61. As you see, the built-in variables are Kontakt's way of providing you with information about note events and other things.

Before we go on, note that variable names cannot contain spaces, and that's why the underscore character _ is often used as a replacement in names which consist of several words. All built-in variables have capital-only names.

Using functions and built-in variables together

Let's look at a script which uses built-in variables together with functions:

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

As I said above, at the time the note callback is executed the $EVENT_NOTE and $EVENT_VELOCITY variables will automagically contain values corresponding to the pressed note and its velocity. So in the the script above we tell Kontakt to play the note with note number $EVENT_NOTE plus 12 - the incoming note transposed up one octave - at the same velocity as the original note. As an exercise you could try adding an init callback to this script, setup a knob that determines the amount of transposition used and then transpose the incoming note by this user-configurable number of notes instead of the currently fixed 12.

All this time we have heard the original note together with the artificially generated note. To mute the original you can use another function, called ignore_event which stops a certain note event from propagating to the Kontakt engine. This function requires you to specify one parameter: the ID number of the note event you want to hinder. So what is this ID? Well, since a script deals with multiple notes we must have some way of specifying exactly which one we mean. Pitch isn't enough since multiple notes of the same pitch may be played at the same time, so Kontakt assigns a unique identification number to each note. The built-in variable $EVENT_ID contains the id number of the note that triggered the callback, so if we pass this to the ignore_event function it will stop the original note from being played:

on note
  ignore_event($EVENT_ID)                               { stop original note   }
  play_note($EVENT_NOTE+12, $EVENT_VELOCITY, 0, -1)     { play transposed note }
end on

Let's try some other functions as well:

on note
  change_note($EVENT_ID, $EVENT_NOTE + 12)     { change pitch, transpose 12 steps upwards   }
  change_velo($EVENT_ID, $EVENT_VELOCITY / 2)  { change velocity to half or original        }
  change_pan($EVENT_ID, -1000, 0)              { change panning, pan left                   }
  change_vol($EVENT_ID, -5000, 0)              { change volume, turn it down 5000 milli-dB  }
  change_tune($EVENT_ID, 50000, 0)             { change tuning, tune up 50000 millicents    }
end on

The names and comments make it pretty clear what they do so let's concentrate on the parameters. All these functions take the ID of the note to change as their first parameter. The second parameter of the functions represent, in order: note number (0 to 127), velocity (0 to 127), panning (-1000 to 1000), volume (in milli-dB), tuning (in millicents).

Note number and velocity of a note event can only be changed before Kontakt has started playing the note. After that any changes will have no effect. On the other hand panning, volume and tuning of a note event may be changed continously while the note is being held pressed. This can be done by invoking the last three functions multiple times for one and the same note event, providing different values as parameters each time. KSP lets you do these changes relative to the current value - in which case the third parameter should be 1, or relative to the original value in which you should specify 0 as third parameter. If you use change_vol to turn the volume down 5 dB and call this function twice using 0 as last parameter, the volume will go down 5 dB. If you do the same but use 1 as last parameter, the volume will go down 10 dB since the change is now relative to the current value and not the original one and the changes accumulate.

As an exercise try to add an init callback, declare some knob or value edit variables and replace some of the parameter values in the example above with your variables to make it possible for the user of the script to set the amount of change. When declaring the user interface controls also try using the scaling value, eg. to present tuning in cents instead of millicents.

Conditional execution

Let's return to the problem solving building blocks. So far you have learnt how to use memory (by using variables) and perform operations in callbacks. But the operations inside a callback have been executed sequentially (one line after the other) in all scripts so far. To do more complex things one has to be able to make decisions - to say that if this the case then do this, otherwise do that. That is, to be able to execute script lines conditionally (only if a certain condition is met. Here's an example that shows conditional execution:

on init
  declare ui_value_edit $tuning(-100000, 100000, 1000)  { amount of detuning }
  declare ui_button $tuning_active                      { detuning on/off }
end on

on note
  if ($tuning_active = 1)                    { if the button is in it's pressed state then ... }
    change_tune($EVENT_ID, $tuning, 0)       { change tuning by user-specified amount          }
  end if
end on

In the init callback two variables are declared. The first one represents the amount of detuning. The range is specified as -100 000 to 100 000. Since this is in millicents it corresponds to -100 to 100 cents. We want to display this as cents to the user so we specify 1000 as scaling value - the millicent value will be divided by 1000 before presentation. The second variable is a button. If you recall it will have the value 0 when the button is unpressed/inactive and 1 when it's pressed/active. We will want to use this button to turn the detuning on or off depending on whether it's pressed or not.

In the note callback a new piece of the script language is introduced: the if-statement. To use it you write if followed by a logical condition inside parenthesis (the parenthesis are mandatory). Then follows a couple of script lines - only one in this case - that should only be performed if the condition was true and skipped otherwise. You indicate the end of these by writing end if. It's also possible to add an else-part if you want to say what should be done if the condition was false, like this:

on note
  if ($tuning_active = 1)
    { lines to be executed if the condition is true }
 else
    { lines to be executed if the condition is false }
  end if
end on

Logical conditions

Logical conditions are very much like mathematical expressions, but instead of evaluating to an integer value they yield true or false. We have already seen an example of a logical condition in the if-statement above. Here are the basics, a condition is formed by comparing mathematical expressions using:

=equal
#not equal
<less than
>greater than
<=less than or equal
>=greater than or equal
Furthermore, you may combine or negate conditions using:
andboth conditions true
orone or both conditions true
notcondition not true
Here are some examples of conditions used in if-statements:

Iterative execution

Now we are going to introduce the final missing building block mentioned in the problem solving section: iterative execution - doing something over and over again. Here's an example of how to repeat something:
on note
  ignore_event($EVENT_ID)                                { ignore original note               }
  while ($NOTE_HELD = 1)                                 { repeat while the original is held: }
    play_note($EVENT_NOTE, $EVENT_VELOCITY, 0, 200000)   {   play note of length 0.2 seconds  }
    wait(200000)                                         {   wait 0.2 seconds                 }
  end while
end on

Before explaining this let's just introduce a new builtin-variable: $NOTE_HELD. This variable will contain the value 1 for as long as the note that triggered the note callback is still held, but as soon as the triggering note is released KSP will assign the value 0 to it (this happens automatically behind the scenes).

At the first line we ignore the original note event (stop the midi event from propagating). Then a new construct is introduced: while. It works very much like an if-statement, but if the condition is true and the lines in it's body are executed it afterwards goes back and checks the condition again. If the condition is still true it executes the lines once again and so on while the condition is true. Since it goes back in this way, it's often referred to as a while-loop. I'm sure you realize that it's important that the logic condition evaluates to false at some point or one would get stuck in the loop. Please try the script out.

Iteration and arrays

A common way use of while loops is for iterating through the elements of an array variable and do something with each element. To test this let's build a simple arpeggiator script which uses an array to hold the notes to use. Before looking at the script I'd like to introduce two new functions:

Let's look at the script now:

on init
  declare %notes[8] := (0, 4, 7, 4, 12, 4, 7, 4)
  declare $i
end on

on note
  ignore_event($EVENT_ID)         
  $i := 0                         { assign the value 0 to $i }  
  while ($i < 8)                  { repeat while $i is less than 8 }
    play_note($EVENT_NOTE + %notes[$i], $EVENT_VELOCITY, 0, 100000)
    wait(100000)   
    inc($i)                       { increment $i by one }  
  end while
end on

If the user presses say C3 the script is supposed to play the notes C3-E3-G3-E3-C4-E3-G3-E3. The %notes array declared in the init callback contains eight values which correspond to the offsets of the notes we want to play relative to the note the user played. Eg. the first note has offset 0 since C3 and C3 are identical, the second note (E3) has offset 4 from C3 and the third note (G3) has offset 7 from C3, and so on. We will see how the variable $i is used later.

In the note callback we start with ignoring the incoming note and setting $i to zero. Then there's a while loop that continue to run for as long as $i is less than eight. If you look at the last line in the while-loop body - "inc($i)" - you see that the last thing that is done in every iteration is to increment the value of $i by one. So the first time around $i will equal zero, the second time it will have been incremented to 1, the third time it will have been further incremented to 2, and so on. Eventually it will reach the value 8 which makes the while condition false and thus causes the loop to stop. Hence the body of the while loop will be executed eight times and $i will take on the values 0 to 7 in order. But the values 0 to 7 represent the indices of the elements in our %notes array (remember that we start counting at zero instead of one), so by writing %notes[$i] we refer to the i'th element which also can be thought of as "the current note offset".

The first line in the while-loop body plays the original note transposed by the current note offset for 0.1 seconds. On the next line we wait for 0.1 seconds to let the note finish before moving on. Please try this script out yourself.

Ok, but let's say we wish to play this over and over again until the note that originally triggered the whole thing is released. Then we simply wrap the above solution inside an outer while-loop that continues while the note is still being held. As you see while-loops can be nested:

on init
  declare %notes[8] := (0, 4, 7, 4, 12, 4, 7, 4)
  declare $i
end on

on note
  ignore_event($EVENT_ID)           
  while ($NOTE_HELD = 1)
    $i := 0                                           { assign the value 0 to $i }  
    while ($i < 8 and $NOTE_HELD = 1)                 { repeat while $i is less than 8 }
      play_note($EVENT_NOTE + %notes[$i], $EVENT_VELOCITY, 0, 100000)       
      wait(100000)   
      inc($i)                                         { increment $i by one }  
    end while
  end while
end on

The $NOTE_HELD = 1 condition was added to the inner while-loop as well to ensure that the arpeggiator stops as soon as the note is released. Without this it would first play the remaining of the eight notes and then stop.

Polyphonic variables

The above script is working nice, but it has a problem: try playing two notes which lie two octaves apart at the same time. You would expect to hear the same arpeggio played using the two keys as base notes, but actually you will only hear every other note played. The reason for this is that both of the two notes will trigger the note callback and just like the tones being played simultaneously we will have two instances of the note callback running more or less in parallel. The problem stems from both of these using and changing the $i variable. Since both of them increments $i by 1, the net effect will be that $i is increased by 2 instead of 1 at each step of the inner loop.

What we would need is a way of telling KSP that each note/release callback that is triggered should keep its own value of the variable $i - to enable us to play polyphonically without the callback instances interfering with each other. This can be done by using the word "polyphonic" in the declaration of $i like this (please try changing the declaration of $i above accordingly):

declare polyphonic $i

Whereas a normal (nonpolyphonic) variable holds one single value that is accessible from all callbacks a polyphonic variable is tied to a certain note event. Just like some built-in variables such as $EVENT_ID and $EVENT_NOTE they can therefor only be accessed in the note and release callback. If two instances of a callback are executed in parallel and one of them changes the value of a polyphonic variable this change will not be visible to the other - they both keep their own value. Apart from providing you with a way to avoid having parallel callbacks interfer, polyphonic variables also provide a way to pass information from the note callback to the release callback. If you assign a value to a polyphonic variable in the note callback the value be retained in the release callback corresponding to that note. So to summarize you will need to make a variable polyphonic when:

Polyphony and the callback execution model

This section is for advanced users who want to better understand the background of the two recommendation of when to use polyphonic variables given above. Feel free to skip this if you like.

Exactly how are the callbacks executed you may wonder when two notes are played (almost) simultaneously. Well, the execution model of KSP is not documented by NI, but it likely works like this:
The execution of callbacks is not multithreaded - two notes will not cause two instances of say the note/release callbacks to actually run "simultaneously". Instead the callbacks run one after another more or less in the order events that triggered them occured. However, the one exception to this is the function wait. If KSP were to wait and do nothing until the waiting time has passed, other callback triggerings would suffer a very noticable latency, eg. you wouldn't be able to play two arpeggios at the same time because the second callback would have to wait for the first to complete. Instead when you invoke the wait function KSP will put the current callback on hold so to say, and continue with any other pending callbacks and after the waiting time has passed later resume executing the current one. Apart from the wait function while loops can also cause significant delays if they would be allowed to run for a long time. Because of this Kontakt impose an upper limit of 49 999 iterations. If you need more iterations than that you will have to call the wait function which gives Kontakt a chance to handle other callbacks. Calling wait will reset the control counter making it possible to do a maximum of another 49 999 iterations before calling it again.

The problem with two triggerings of the same callback interfering with each as in the arpeggiator script above occurs just because we use the wait function. Say the notes are played at the same time, then KSP will start to execute one of the note callbacks. When it reaches the wait line it will put the callback on hold and continue with the second triggering. That callback will also reach the wait line and since there are no other note events to handle KSP will simply wait doing nothin until the first callback has finished.

In short, callback execution is not multithreaded but calling the wait function will interweave multiple callbacks. If you use and change a certain variable in a callback but don't use the wait function, multiple triggerings of callbacks won't cause you problems since the callbacks will then be executed sequentially and not in parallel. However you may still benefit from using polyphonic variables since they provide a way to pass information from a note callback to the corresponding release callback.

Group functions

KSP has a pair of functions which enable you to specify which groups to enable/disable when playing a note. Here's an example:

on note
  disallow_group($ALL_GROUPS)
  allow_group(0)
  allow_group(2)
end on

The allow_group and disallow_group functions both take an integer parameter corresponding to the group you want to turn on or off. Group indices start at 0, so if your instrument has 10 groups the indices lie between 0 and 9. There's a builtin constant variable with name $ALL_GROUPS that you can pass to either of these functions to indicate that all groups should be turned on/off (depending on which of the function you use). The above script will turn off all groups and then turn the first and third group on, ensuring that those are the only enabled groups.

Release triggers

In some cases you want or are forced to handle release triggers in a customized way. This need arises when you:

Changing the volume, panning or tuning of a note won't change its release sample. Furthermore the allow_group and disallow_group functions have no effect on release triggers unless you completely turn off Kontakt's builtin release trigger handling and handle things manually. Both of these facts I consider bad design (see my forum post about this) but we'll have to live with it until NI makes it better.

Let's look at a script that changes the panning of a note. Try loading this script on an instrument that has release triggers and you'll hear that the note is panned but the release sample is unaffected:

on note
  change_pan($EVENT_ID, -1000, 0)      { pan left }
end on

Clearly since Kontakt doesn't do the job, we will have to do the release playing ourselves. The first thing we must do is to turn off the built-in release triggering functionality which is done by including SET_CONDITION(NO_SYS_SCRIPT_RLS_TRIG) in the init callback.

If Kontakt had been better designed the two first of these would have been solved by automatically mirroring any change made to a note to the corresponding release trigger sample (eg. if you changed the tuning of a note by 10 cents the release sample would also be tuned that way), and by enabling you to use the allow_group/disallow_group functions to turn on or off release trigger groups. Unfortunately this is not the case. As soon as you want to do any of the above you will need to turn off the built-in release triggering functionality alltogether. This will essentially make the release trigger groups work just like normal groups. You then have to use play release triggers yourself by first the release trigger group and no other groups using the allog_group/disallow_group functions, and then use play_note to play the release sample. Although this way of handling releases gives you a lot of flexibility, it also also causes unneeded complexity in the two first and most common use cases above (I've written a summarizing the problems).

More sections...

Text needs to be written...


[1] KONTAKT is a registered trademark of NATIVE INSTRUMENTS Software Synthesis GmbH.