Sie sind auf Seite 1von 16

Contents

nilsliberg.se/ksp/scripts/tutorial/

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:
change volume/panning/tuning of a specific note
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)
control a wide range of Kontakt parameters (maybe in the Kontakt 2.1 update)
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.

1/16

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:
Keep certain things in memory and,
Perform certain operations:
sequentially (one after another),
conditionally (only if a certain condition is true), or
iteratively (over and over again until some condition is met that makes us go on with something else).
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

2/16

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

3/16

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:

4/16

127 / 10
$x + 1
$x * 10
((-$y + 1) * $x) - 100
$sum / $n
$sum mod $n
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

5/16

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:
Your script will be easier to understand since a variable name such as $max_velocity provides information
about what the number signifies. In a complex script $max_velocity would be easier to understand than the
value 100.
If you decide to change the value from 100 to 110 you only have to make the change at one place in the
script. Had you not used a constant variable you might have to change this value at thirty different places,
maybe forgetting one occurance thereby introducing a bug.

User interface controls


Scripts can use different types of user interface controls to let the end-user specify settings:
knobs
value edit boxes (which you can double-click on to enter a value)
menus
buttons
text labels
tables
A user interface control can be seen as a special kind of variable whose value is not only used internally in the script

6/16

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.

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.

7/16

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)
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)
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 ui_knob $velocity(0, 127, 1)
$note := 60
$velocity := 100
end on

8/16

on note
play_note($note, $velocity, 0, -1)
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:
predeclared - they aren't and shouldn't be declared with a "declare" line in your script.
read-only - you cannot assign a value to a built-in variable. However the KSP engine can, and it assigns
values to these variables that you can later use.
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


9/16

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 userconfigurable 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)
play_note($EVENT_NOTE+12, $EVENT_VELOCITY, 0, -1)
end on
Let's try some other functions as well:
on note
change_note($EVENT_ID, $EVENT_NOTE + 12)
change_velo($EVENT_ID, $EVENT_VELOCITY / 2)
change_pan($EVENT_ID, -1000, 0)
change_vol($EVENT_ID, -5000, 0)
change_tune($EVENT_ID, 50000, 0)
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.

10/16

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)
declare ui_button $tuning_active
end on
on note
if ($tuning_active = 1)
change_tune($EVENT_ID, $tuning, 0)
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)
else
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:

11/16

equal

not equal

<

less than

>

greater than

<=

less than or equal

>=

greater than or equal

Furthermore, you may combine or negate conditions using:

and

both conditions true

or

one or both conditions true

not

condition not true

Here are some examples of conditions used in if-statements:


if ($x = $y)
if ($x # $y)
if ($x <= $max)
if ($y < 10 and $x < 10)
if ($EVENT_NOTE > 60 and $EVENT_VELOCITY < 100)
if (not ($x = 100 or $y = 100))

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)
while ($NOTE_HELD = 1)
play_note($EVENT_NOTE, $EVENT_VELOCITY, 0, 200000)
wait(200000)
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).

12/16

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:
inc($x) - "inc" is short for "increment" and this function increments the value of the given variable by 1.
dec($x) - "dec" is short for "decrement" and this function decrements the value of the given variable by 1.
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
while ($i < 8)
play_note($EVENT_NOTE + %notes[$i], $EVENT_VELOCITY, 0, 100000)
wait(100000)
inc($i)
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.

13/16

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
while ($i < 8 and $NOTE_HELD = 1)
play_note($EVENT_NOTE + %notes[$i], $EVENT_VELOCITY, 0, 100000)
wait(100000)
inc($i)
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:
The variable is used and and its value changed in the note/release callback, and the callback invokes the wait
function, and your instrument is supposed to support polyphonic playing.

14/16

You want to pass a value from the note callback to the release callback without risking the value being
changed by intermediate notes/releases.

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
15/16

In some cases you want or are forced to handle release triggers in a customized way. This need arises when you:
Change the volume, panning or tuning of a note.
Want to turn release triggers on or off or choose what release trigger 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)
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.

16/16

Das könnte Ihnen auch gefallen