Beruflich Dokumente
Kultur Dokumente
Type in your script in the script text editor (the rest of the tutorial will
show you how to write the scripts).
Scripts can be seen as small plugins. They are quite similar to the MIDI effect It's possible to use a couple of shortcuts in the editor:
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 Ctrl+A - select all
to some internal Kontakt features which in addition lets them: Ctrl+X - cut
Ctrl+C - copy
change volume/panning/tuning of a specific note
Ctrl+V - paste
fade in/out a specific note
specify which groups should be used for playing back a specific note
start playback of a note at any sample offset (only in Sampler mode) 5. Important: if you're accustomed to using Ctrl+Z for undo be sure to
control a wide range of Kontakt parameters (maybe in the Kontakt 2.1 avoid it in the script editor as it'll discard your changes and you may
update) loose all of what you have typed in.
6. Press the "Apply" button for the changes to take effect. The little box to
Scripts are written in the Kontakt Script Language and entered as plain text. A the left of the Apply button will light up whenever there are unapplied
script basically consists of a number text lines with instructions to the Kontakt changes.
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.
To create and edit your own script in Kontakt you go through these steps (see
image below):
The process of writing a script consists of first going through steps 1-5 and then
1. Make sure the Script Editor is active. If not activate it. repeating steps 4-5 (editing and applying changes) until the script works as it
2. Press the "Edit" button to show the text editor where the script is should. A quick way to test any of the example scripts below is to copy it from
entered this page, and then press Ctrl+A followed by Ctrl+V in the Kontakt Script Editor to
3. Specify a title for your script. Make sure to press the enter key after replace any previous text with the script you want to try out, followed by clicking
having typed this in or Kontakt will not remember the title properly. The the Apply button.
title is completely up to you, choose something descriptive.
Editing scripts 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.
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. Callbacks
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 Scripts have four main parts called callbacks. A callback represents a task that is
well use any text editor of your choice to enter the source code, and then copy to be performed when some event occurs (eg. a note is pressed or the script is
and paste it into the Kontakt script editor (followed by clicking Apply). 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
So why not use internal Kontakt script editor from start? Well, the reasons may think of someone calling you back on the phone letting you know something has
vary: you may think the text size is too small, you want to use the undo function happened, prompting you to do something). But it's still your task to say what
of your text editor, want an editor that does automatic indentation and code should happen. Eg. when a note is pressed KSP triggers the note callback where
coloring etc. For really short scripts it's probably not worth the extra work of you have specified what then should happen. The following script illustrates how
copying and pasting from an external editor, but for larger scripts I would like to these four parts are written:
recommend using my freely available KScript Editor (available both for Mac OSX
and Windows). on init
end on
When KSP executes one of your script lines it will start by evaluating all The size of the array is specified within brackets to the right of the name. Arrays
expressions it contains. For every variable in the expression the current value of in Kontakt scripts are always created with a fixed size that cannot be changed
the variable will be used. You can picture this as if every variable would have afterwards.
been substituted by the value it contains at the time the expresion is evaluated.
To access a specific element you write the variable name followed by the Constant variables
element index within brackets:
Sometimes it's desirable to prevent a variable's value from being changed after
on init the declaration - to make the variable constant. To do this you use the word
declare %note_tunings[12]
%note_tunings[0] := 10
const in the declaration:
%note_tunings[1] := -3
%note_tunings[2] := -15 on init
%note_tunings[3] := %note_tunings[2] - 10 declare const $max_velocity := 100
end on end on
The above example assigns values to the first four elements. Array elements are This variable behaves like any other with the exception that it's not possible to
initialized to zero by default, so the elements not assigned to above will contain assign a new value to it (trying to do this will generate an error message). After
the value zero. An array element (written as the array name followed by the having declared the constant you can use the variable name anywhere you would
element index inside brackets) behaves in every way just like a normal variable. have written 100. So why do this instead of actually writing the value 100 which
As you see the indexing does not start counting at 1 but instead at 0 - the first seems a lot easier? Well, using a constant variable instead has two benefits:
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 Your script will be easier to understand since a variable name such as
therefor has distance 0, the second element lies at offset 1 from the beginning, $max_velocity provides information about what the number signifies. In
the third at 2 and so on. a complex script $max_velocity would be easier to understand than the
value 100.
Note that the value inside brackets on the declaration line has a different If you decide to change the value from 100 to 110 you only have to
meaning than values inside brackets on other lines. On the declaration the value make the change at one place in the script. Had you not used a constant
signifies array size, and on the other lines it signifies element index. Finally we variable you might have to change this value at thirty different places,
shall note that like the declaration and first assignment of a normal variable may maybe forgetting one occurance thereby introducing a bug.
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 User interface controls
written inside parenthesis and the values comma-separated, like this:
Scripts can use different types of user interface controls to let the end-user
on init
declare %note_tunings[12] := (10, -3, -15, 5, 4, -8, 6, 3, -22, 0, -2, 7) specify settings:
end on
knobs
This is just a shorter way of writing assignments to the individual elements like value edit boxes (which you can double-click on to enter a value)
above. Naturally the number of values specified must be equal to the size of the menus
array. buttons
text labels
tables
A user interface control can be seen as a special kind of variable whose value is integers at our disposal. Eg. the $humanize_delay variable above ranges from 0
not only used internally in the script but also displayed visually. And it turns out to 1000 milliseconds. However, even though variable values can only be integers
that the syntax for declaring user interface controls is very similar to how it is still possible to have Kontakt present the value in another unit to the user.
ordinary variables are declared. Let's create a button. Please try this script out in That's what the scale factor is used for, the value presented to the user will be
Kontakt and you'll see a button with the caption "mybutton" appear: 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
on init will see it as a setting between 0.000 and 1.000 seconds. The scaling only affects
declare ui_button $mybutton the presentation and setting it to 1 the internally used value will be presented as
end on
is to the user (since division by 1 leaves any number unchanged).
The additional word "ui_button" in the declaration indicates that the variable
Knobs and value edit variables work similarily to the button. That is, you can
$mybutton should appear visually as a button. The variable $mybutton will have
control the knob/value edit by assigning a value to the variable anywhere in your
the value 1 when the button is active/pressed and 0 otherwise. You can also
script, and any change the user makes to the control will change the value.
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 Comments
callbacks using conditional statements. For now let's look at knobs and value
edits as well: 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
on init processor) when executing the script. They have no effect whatsoever, but they
declare ui_knob $volume(-10, 10, 1) provide a way for you to document and explain to readers of the script what you
declare ui_value_edit $humanize_delay(0, 1000, 1000) are doing. Think of commenting as writing a note in the margin of a book - while
end on
not changing the contents of the book they give you information about how to
interprete the text.
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
Comments are to the benefit of other people who might be interested in your
another difference: the parenthesis and the three comma-separated values to
script, but mostly for your own sake. Without them you most likely will have
the right. These three values are mandatory and represent the minimum value,
forgotten your line of thought when you return to your own script after a couple
maximum value and scaling factor respectively. Min and max value is easy to
of days/weeks of not working with it. Use of comments is not a sign of
understand; test the script above and you'll see that value range of volume knob
amateurishness, on the contrary they are often used extensively by experienced
is -10 to 10.
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
But what is the scaling factor? Well since script variables can only represent we're doing. Here's an example of how to use them:
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 on init
use milliseconds or microseconds - if we had used seconds it would only be { volume adjustment (in dB) }
possible to have whole seconds, not fractions like 0.1s since we only have declare ui_knob $volume(-10, 10, 1)
{ random delay for humanizing (in milliseconds) } This function requires of you to provide four pieces of information, or
declare ui_value_edit $humanize_delay(0, 1000, 1000) parameters:
end on
Anything within curley braces - { } - is considered a comment and ignored by KSP. note number (pitch)
a value in the 0-127 range as specified by the MIDI standard.
You can place them anywhere in the code, eg. on their own lines as above or at
note velocity
the end of lines to make the script more compact:
a value in the 0-127 range as specified by the MIDI standard.
sample offset
on init
declare ui_knob $volume(-10, 10, 1) { volume adjustment (in dB) } for instruments which use Sampler mode this value may be used to start
declare ui_value_edit $humanize_delay(0, 1000, 1000) { humanizing delay (in milliseconds) } playback somewhere in the middle of the sample. To start playback from
end on the start of the sample use the value 0. In DFD mode only 0 is supported.
note duration
Finally a note about whitespace. As you may have noticed in the first script above the length of the note in microseconds. After this time a note-off
you can use empty lines wherever you wish to make scripts easier to read - just message will automatically be generated.
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 To use a function you write it's name followed by any parameters (four in this
spaces or tabs, eg. to align things (at many places you still need at least one case) wrapped inside parenthesis separated by comma:
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 on note
optional. However indentation is often used to put emphasis on the structure of play_note(60, 100, 0, 1000000) { play C3 at velocity 100, release after 1 second }
the script - the indentation makes it easier to spot where the init callback begins end on
and where it ends. Like comments extra whitespace only serves to make the code
more comprehensible to humans without affecting how the script works. 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
Functions 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
So far I have shown how to declare variables and assign them values. To actually
words to describe that a function is executed which can be good to know, eg. one
control the behaviour of Kontakt (which is the whole point of using a script) you
say the function is called/invoked/executed/run. Also note, that while we passed
need functions. Think of a function as a named operation/command that Kontakt
simple numbers as parameters to the function above these could also have been
can perform for you whenever your order it to. There are functions for a lot of
mathematical expressions of arbitrary complexity. As an exercise, try changing
tasks: fading in/out, playing notes, releasing notes, changing tuning, etc. Some
some of the four parameters above and notice the different result as you play
functions need you to provide information (called parameters) that controls how
some notes on your keyboard.
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. 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 initialized to the values 60 and 100 respectively. These are only initial values - if
way to do this: just specify -1 for the note duration and Kontakt will handle the the user sets the knobs to other values later these two variables will then contain
rest. Of course -1 is not used as the actual note duration but rather serves as a the changed values. As I said earlier the init callback is triggered only once when
signal that the note duration should be handled in a special way. 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
on note preset menu in Kontakt, but also every time you press the Apply button (causing
play_note(60, 100, 0, -1) { play C3 at velocity 100, release when the triggering note is } the updated version of the script to load!).
end on
In the note callback instead of using constant numbers as we previously did, now
A final note about which notes trigger the note callback. If the artificially
we pass variables as parameters to the function. As mentioned earlier you can
generated note above would trigger the note callback just like "real" notes do,
picture this as if each time the line is executed, all variables would be replaced
using play_note would cause the lines inside the callback to be executed and
with whatever value they happen to contain at the time. As you see, the use of
another note would be played (that is what the above callback does, right) and
variables can make a single script line have different effects at different times
that note would in turn trigger the note callback again, and we would have an
depending on the values of the variables. If you turn the knobs, the values of the
infinite chain reaction. This is of course not desirable and is why KSP is designed
two variables will change and thereby the accompaniment note and its velocity
so that only "real" notes (of external origin) trigger the note callback and not
(try this!).
artificially generated ones.
Built-in variables
Using functions and variables together
There are a number of built-in variables that provide you with information about
Let's now try to bring the two concepts of functions and variables together to
things like tempo, time, MIDI controller states, etc. For example, in the note
make the above example less static:
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
on init
declare ui_knob $note(0, 127, 1) { declare knob to control which note to play }
keyboard). Most of the built-in variables can be used within all callbacks, but
declare ui_knob $velocity(0, 127, 1) { declare knob to control velocity } some of them contain information about the press/release of a note and may
$note := 60 { assign the value 60 to the first knob } therefor only be used in the note and release callbacks (the only places where
$velocity := 100 { assign the value 100 to the second knob } their use makes sense).
end on
on note Now you may wonder what makes builtin-in variables different from the
play_note($note, $velocity, 0, -1) { play the user specified note at given velocity } variables we used earlier. They are different by being:
end on
on note
Conditional execution if ($tuning_active = 1)
{ lines to be executed if the condition is true }
Let's return to the problem solving building blocks. So far you have learnt how to else
{ lines to be executed if the condition is false }
use memory (by using variables) and perform operations in callbacks. But the end if
operations inside a callback have been executed sequentially (one line after the end on
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 Logical conditions
be able to execute script lines conditionally (only if a certain condition is met.
Here's an example that shows conditional execution:
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
on init
declare ui_value_edit $tuning(-100000, 100000, 1000) { amount of detuning } example of a logical condition in the if-statement above. Here are the basics, a
declare ui_button $tuning_active { detuning on/off } condition is formed by comparing mathematical expressions using:
end on
on note = equal
if ($tuning_active = 1) { if the button is in it's pressed state then ... } # not equal
change_tune($EVENT_ID, $tuning, 0) { change tuning by user-specified amount }
< less than At the first line we ignore the original note event (stop the midi event from
> greater than 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
<= less than or equal it afterwards goes back and checks the condition again. If the condition is still
>= greater than or equal true it executes the lines once again and so on while the condition is true. Since it
Furthermore, you may combine or negate conditions using: goes back in this way, it's often referred to as a while-loop. I'm sure you realize
and both conditions true 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.
or one or both conditions true
not condition not true
Iteration and arrays
Here are some examples of conditions used in if-statements:
A common way use of while loops is for iterating through the elements of an
if ($x = $y) array variable and do something with each element. To test this let's build a
if ($x # $y) simple arpeggiator script which uses an array to hold the notes to use. Before
if ($x <= $max) looking at the script I'd like to introduce two new functions:
if ($y < 10 and $x < 10)
if ($EVENT_NOTE > 60 and $EVENT_VELOCITY < 100) inc($x) - "inc" is short for "increment" and this function increments the
if (not ($x = 100 or $y = 100)) value of the given variable by 1.
dec($x) - "dec" is short for "decrement" and this function decrements
Iterative execution the value of the given variable by 1.
Now we are going to introduce the final missing building block mentioned in the Let's look at the script now:
problem solving section: iterative execution - doing something over and over
again. Here's an example of how to repeat something: on init
on note declare %notes[8] := (0, 4, 7, 4, 12, 4, 7, 4)
ignore_event($EVENT_ID) { ignore original note } declare $i
while ($NOTE_HELD = 1) { repeat while the original is held: } end on
play_note($EVENT_NOTE, $EVENT_VELOCITY, 0, 200000) { play note of length 0.2 seconds }
wait(200000) { wait 0.2 seconds } on note
end while ignore_event($EVENT_ID)
end on $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)
Before explaining this let's just introduce a new builtin-variable: $NOTE_HELD. wait(100000)
This variable will contain the value 1 for as long as the note that triggered the inc($i) { increment $i by one }
note callback is still held, but as soon as the triggering note is released KSP will end while
assign the value 0 to it (this happens automatically behind the scenes). end on
If the user presses say C3 the script is supposed to play the notes C3-E3-G3-E3- while ($i < 8 and $NOTE_HELD = 1) { repeat while $i is less than 8 }
play_note($EVENT_NOTE + %notes[$i], $EVENT_VELOCITY, 0, 100000)
C4-E3-G3-E3. The %notes array declared in the init callback contains eight values
wait(100000)
which correspond to the offsets of the notes we want to play relative to the note inc($i) { increment $i by one }
the user played. Eg. the first note has offset 0 since C3 and C3 are identical, the end while
second note (E3) has offset 4 from C3 and the third note (G3) has offset 7 from end while
C3, and so on. We will see how the variable $i is used later. end on
In the note callback we start with ignoring the incoming note and setting $i to The $NOTE_HELD = 1 condition was added to the inner while-loop as well to
zero. Then there's a while loop that continue to run for as long as $i is less than ensure that the arpeggiator stops as soon as the note is released. Without this it
eight. If you look at the last line in the while-loop body - "inc($i)" - you see that would first play the remaining of the eight notes and then stop.
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 Polyphonic variables
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 The above script is working nice, but it has a problem: try playing two notes
and thus causes the loop to stop. Hence the body of the while loop will be which lie two octaves apart at the same time. You would expect to hear the same
executed eight times and $i will take on the values 0 to 7 in order. But the values arpeggio played using the two keys as base notes, but actually you will only hear
0 to 7 represent the indices of the elements in our %notes array (remember that every other note played. The reason for this is that both of the two notes will
we start counting at zero instead of one), so by writing %notes[$i] we refer to the trigger the note callback and just like the tones being played simultaneously we
i'th element which also can be thought of as "the current note offset". 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
The first line in the while-loop body plays the original note transposed by the of them increments $i by 1, the net effect will be that $i is increased by 2 instead
current note offset for 0.1 seconds. On the next line we wait for 0.1 seconds to of 1 at each step of the inner loop.
let the note finish before moving on. Please try this script out yourself.
What we would need is a way of telling KSP that each note/release callback that
Ok, but let's say we wish to play this over and over again until the note that is triggered should keep its own value of the variable $i - to enable us to play
originally triggered the whole thing is released. Then we simply wrap the above polyphonically without the callback instances interfering with each other. This
solution inside an outer while-loop that continues while the note is still being can be done by using the word "polyphonic" in the declaration of $i like this
held. As you see while-loops can be nested: (please try changing the declaration of $i above accordingly):
Exactly how are the callbacks executed you may wonder when two notes are Group functions
played (almost) simultaneously. Well, the execution model of KSP is not
documented by NI, but it likely works like this: KSP has a pair of functions which enable you to specify which groups to
The execution of callbacks is not multithreaded - two notes will not cause two enable/disable when playing a note. Here's an example:
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 on note
triggered them occured. However, the one exception to this is the function wait. disallow_group($ALL_GROUPS)
allow_group(0)
If KSP were to wait and do nothing until the waiting time has passed, other allow_group(2)
callback triggerings would suffer a very noticable latency, eg. you wouldn't be end on
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 The allow_group and disallow_group functions both take an integer parameter
KSP will put the current callback on hold so to say, and continue with any other corresponding to the group you want to turn on or off. Group indices start at 0,
pending callbacks and after the waiting time has passed later resume executing so if your instrument has 10 groups the indices lie between 0 and 9. There's a
the current one. Apart from the wait function while loops can also cause builtin constant variable with name $ALL_GROUPS that you can pass to either of
significant delays if they would be allowed to run for a long time. Because of this these functions to indicate that all groups should be turned on/off (depending on
Kontakt impose an upper limit of 49 999 iterations. If you need more iterations 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 10 cents the release sample would also be tuned that way), and by enabling you
groups. 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
Release triggers 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
In some cases you want or are forced to handle release triggers in a customized
release trigger group and no other groups using the allog_group/disallow_group
way. This need arises when you:
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
Change the volume, panning or tuning of a note. complexity in the two first and most common use cases above (I've written a
Want to turn release triggers on or off or choose what release trigger summarizing the problems).
groups to use for a specific note
Want to play custom release notes.
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