You are on page 1of 14

What are scripts?

4.

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.

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

5.

6.

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.
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.

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.
2.
3.

Make sure the Script Editor is active. If not activate it.


Press the "Edit" button to show the text editor where the script is
entered
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.

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:
o sequentially (one after another),
o conditionally (only if a certain condition is true), or
o 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 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.

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).

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

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).

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
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:

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.

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:

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 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.

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

on note
play_note($note, $velocity, 0, -1)
end on

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

Using functions and variables together

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

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!).

{ play the user specified note at given velocity }

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

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


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 millidB), 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 ifstatement. 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:
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))

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:

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).

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
{ 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-E3C4-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:

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.
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
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

{ pan left }

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).

You might also like