Skip to main content

Advanced Concepts

Preprocessor & System Scripts

SET_CONDITION(<condition-symbol>) 

Define a symbol to be used as a condition

RESET_CONDITION(<condition-symbol>)

Delete a definition

USE_CODE_IF(<condition-symbol>)

...

END_USE_CODE

Interpret code when <condition> is defined

USE_CODE_IF_NOT(<condition-symbol>)

...

END_USE_CODE

Interpret code when <condition> is not defined

NO_SYS_SCRIPT_GROUP_START 

Condition; if defined with SET_CONDITION(), the system script which handles all group start options 

NO_SYS_SCRIPT_PEDAL 

Condition; if defined with SET_CONDITION(), the system script which sustains notes when CC# 64 is received will be bypassed

NO_SYS_SCRIPT_RLS_TRIG 

Condition; if defined with SET_CONDITION(), the system script which triggers samples upon the release of a key is bypassed

reset_rls_trig_counter(<note>) 

Resets the release trigger counter (used by the release trigger system script)

will_never_terminate(<event-id>)

Tells the script engine that this event will never be finished (used by the release trigger system script)

Examples

A preprocessor is used to exclude code elements from interpretation. Here's how it works:

USE_CODE_IF(<condition>)

...

END_USE_CODE

or

USE_CODE_IF_NOT(<condition>)

...

END_USE_CODE

<condition> refers to a symbolic name which consists of alphanumeric symbols, preceded by a letter. You could write for example:

on note
	{do something general}
$var := 5

{do some conditional code}
USE_CODE_IF_NOT(dont_do_sequencer)
	while ($count > 0)
		play_note()
	end while
END_USE_CODE
end on

What's happening here?

Only if the symbol dont_do_sequencer is not defined, the code between USE_ and END_USE will be processed. If the symbol were to be found, the code would not be passed on to the parser; it is as if the code was never written. Therefore it does not utilize any CPU power.

You can define symbols with SET_CONDITION(<condition symbol>)

and delete the definition with RESET_CONDITION(<condition symbol>)

All commands will be interpreted before the script is running, i.e., by using USE_CODE_ , the code might get stalled before it is passed to the script engine. This means, SET_CONDITION and RESET_CONDITION are not actually true commands: they cannot be utilized in if()...end if statements; also a wait() statement before those commands is useless. Each SET_CONDITION and RESET_CONDITION will be executed before something else happens.

All defined symbols are passed on to following scripts, i.e. if script 3 contains conditional code, you can turn it on or off in script 1 or 2.

You can use conditional code to bypass system scripts. There are three built-in symbols:

  • NO_SYS_SCRIPT_PEDAL

  • NO_SYS_SCRIPT_RLS_TRIG

  • NO_SYS_SCRIPT_GROUP_START

If you define one of those symbols with SET_CONDITION(), the corresponding part of the system scripts will be bypassed. For clarity reasons, those definitions should always take place in the init callback.

on init
	{we want to do our own release triggering}
	SET_CONDITION(NO_SYS_SCRIPT_RLS_TRIG)
end on

on release
	{do something custom here}
end on 

PGS

It is possible to send and receive values from one script to another, discarding the usual left-to-right order by using the Program Global Storage (PGS) commands. PGS is a dynamic memory that can be read/written by any script. 

PGS commands

pgs_create_key(<key-id>,<size>)

pgs_key_exists(<key-id>)

pgs_set_key_val(<key-id>,<index>,<value>)

pgs_get_key_val(<key-id>,<index>)

<key-id> is similar to a variable name; it can only contain letters and numbers and must not start with a number. It is a good idea to always write them in capitals to emphasize their unique status.

Here's an example, insert this script into any slot:

on init
	pgs_create_key(FIRST_KEY, 1) {defines a key with 1 element}
	pgs_create_key(NEXT_KEY, 128) {defines a key with 128 elements}
	declare ui_button $Just_Do_It
end on

on ui_control($Just_Do_It)

	{writes 70 into the first and only memory location of FIRST_KEY}
	pgs_set_key_val(FIRST_KEY, 0, 70)


	{writes 50 into the first and 60 into the last memory location of NEXT_KEY}
	pgs_set_key_val(NEXT_KEY, 0, 50)
	pgs_set_key_val(NEXT_KEY, 127, 60)
end on

and insert the following script into any other slot:

on init
	declare ui_knob $First (0,100,1)
	declare ui_table %Next[128] (5,2,100)
end on
on pgs_changed

	{checks if FIRST_KEY and NEXT_KEY have been declared}
	if(pgs_key_exists(FIRST_KEY) and _pgs_key_exists(NEXT_KEY))
		$First := pgs_get_key_val(FIRST_KEY,0) {in this case 70}
		%Next[0] := pgs_get_key_val(NEXT_KEY,0) {in this case 50}
		%Next[127] := pgs_get_key_val(NEXT_KEY,127) {in this case 60}
	end if
end on

As illustrated above, there is also a callback that is executed whenever a set_key command has been executed.

on pgs_changed 

Callback type, executed whenever any pgs_set_key_val() is executed in any script

It is possible to have as many keys as you want, however each key can only have up to 256 elements.

The basic handling for PGS strings is the same as for normal PGS keys; there’s only one difference: PGS strings keys aren’t arrays like the standard PGS keys you already know – they resemble normal string variables.

PGS strings commands

pgs_create_str_key(<key-id>)

pgs_str_key_exists(<key-id>)

pgs_set_str_key_val(<key-id>,<stringvalue>)

<stringvalue> := pgs_get_str_key_val(<key-id>)

<key-id> is something similar to a variable name. It can only contain letters and numbers and must not start with a number. It is a good idea to always write them in capitals to emphasize their unique status.

Zone and Slice Functions

find_zone(<zone-name>) 

Returns the zone ID for the specified zone name. Only available in the init callback.

get_sample_length(<zone-ID>) 

Returns the length of the specified zone's sample in microseconds

num_slices_zone(<zone-ID>) 

Returns the number of slices in the specified zone

zone_slice_length(<zone-ID>,<slice-index>) 

Returns the length in microseconds of the specified slice with respect to the current tempo

zone_slice_start(<zone-ID>,<slice-index>) 

Returns the absolute start point of the specified slice in microseconds, independent of the current tempo

zone_slice_idx_loop_start(<zone-ID>,<loop-index>) 

Returns the index number of the slice at the loop start

zone_slice_idx_loop_end(<zone-ID>,<loop-index>) 

Returns the index number of the slice at the loop end

zone_slice_loop_count(<zone-ID>,<loop-index>) 

Returns the loop count of the specified loop

dont_use_machine_mode(<ID-number>) 

Play the specified event in sampler mode

User-defined Functions

function <function-name>

...

end function

Declares a function

call <function-name> 

Calls a previously declared function

Remarks

  • The function has to be declared before it is called.

Examples

on init
	declare $root_note := 60
	
	declare ui_button $button_1
	set_text ($button_1,"Play C Major")
	
	declare ui_button $button_2
	set_text ($button_2,"Play Gb Major")
	
	declare ui_button $button_3
	set_text ($button_3,"Play C7 (b9,#11)")
end on
function func_play_triad
	play_note($root_note,100,0,300000)
	play_note($root_note + 4,100,0,300000)
	play_note($root_note + 7,100,0,300000)
end function
on ui_control ($button_1)
	$root_note := 60
	call func_play_triad
	$button_1 := 0
end on
on ui_control ($button_2)
	$root_note := 66
	call func_play_triad
	$button_2 := 0
end on
on ui_control ($button_3)
	$root_note := 60
	call func_play_triad
	$root_note := 66
	call func_play_triad
	$button_3 := 0
end on

Jazz Harmony 101

Resource Container

Introduction

The Resource Container is a useful tool for library developers. It is a dedicated location to store scripts, graphics, .nka files and impulse response files that can be referenced by any NKI or group of NKIs that are linked to the container. Another benefit is that you can create a resource container monolith file containing all the scripts, graphics etc, so that you can easily move them around or send them to other team members. When loading an NKI, the resource container is treated like a sample, so if it is not found it will appear in the Samples Missing dialogue.

Setup

To create a Resource Container for your NKI, open up its instrument options and click the <Create>; button beside the area labeled as Resource Container. After creating a new resource container file, KONTAKT checks if there is already a resource folder structure available. If there isn’t, you can let KONTAKT create it for you. If you do this, you will find Resources and Data folders next to the NKR file you just created.

The Resources folder is the place where you can store the files that an NKI can use, which are not samples. As you can see KONTAKT has already created several subfolders for you: ir_samples, pictures (for GUI graphics and wallpapers), data (for .nka files) and scripts. The only thing to do now is to move your files into the right folders and you are ready to go.

Working with the Resource Container

Let’s say you’re creating a new library: after setting up the Resource Container as described above, you can tell all of the NKIs that are part of your library to use this special Resource Container. Just open up the NKI’s instrument options and use the Browse function.

As long as the Resources folder exist besides the NKR file (this is the Resource Container monolith), KONTAKT will read all files directly from this folder structure.

For loading scripts from the scripts subfolder, use the “Apply from… -> Resources folder” function within the script editor.

Now let’s say you want to send your current working status to another team member. Open up the instrument options, click the Create button and then overwrite your NKR file. Be aware that this will completely overwrite your monolith, it won’t be matched in any way. Now KONTAKT will do all of the following:

  • Check the ir_samples subfolder for any .wav, .aif, .aiff or .ncw files and put them into the monolith.

  • Check the pictures folder for any .tga or .png files that also have a .txt file of the same filename next to them. All of these will be packed into the monolith. Note that wallpapers also need a .txt file or they will be ignored.

  • Check the scripts subfolder for any .txt files which will then be put into the monolith.

  • Check the data subfolder for any .nka files which will then be put into the monolith.

  • Check the performance_view folder for any .nckp files containing performance views created by Creator Tools.

After that rename your Resources folder and reopen your NKI. Now that there is no Resources folder present anymore, KONTAKT will automatically read from the NKR monolith file. If everything is still working as expected you can send your NKIs and the NKR monolith to your team member.

To continue your work just rename the Resources folder back to “Resources”.

Remarks

  • The Resource Container will be checked in the samples missing dialog.

  • When you save your NKI as a monolith file, the Resource Container will not be integrated into the monolith. The path to the Resource Container will be saved in absolute path mode.

Changing FX from KSP

Introduction

Prior to KONTAKT 5.5, there was already the infrastructure in place to get info about the content of effect slots via engine parameter variables like $ENGINE_PAR_EFFECT_TYPE and built-in constants like $EFFECT_TYPE_FILTER (refer to Module Status Retrieval).

Starting with KONTAKT 5.5, it is also possible to change FX with the same set of built-in variables.

Example

on init
	set_engine_par($ENGINE_PAR_EFFECT_TYPE,$EFFECT_TYPE_FILTER,0,0,-1)
	set_engine_par($ENGINE_PAR_EFFECT_SUBTYPE, $FILTER_TYPE_LDR_LP4,0,0,-1)
end on

Inserts a 4 pole lowpass ladder filter into the first group slot

on async_complete callback

Changing FX slot contents is an asynchronous operation. This means, one cannot reliably access the newly instantiated effect immediately after instantiation. To resolve this, the command returns an $NI_ASYNC_ID and triggers the on async_complete callback. In addition, wait_async() command can be used to streamline this process.

Default Filter Type

Filters are somewhat special as they are effect types that feature subtypes. Since one can now instantiate a new filter from KSP without explicitly selecting its subtype, there is a need for a predefined default filter subtype. This is SV LP4.

Implications on Modulation and Automation assignments

When changing the contents of FX slots through KSP, it is expected that the handling of assigned automation and modulation is identical to performing the same action using KONTAKT's GUI.

  • When changing a slot's effect type or removing it entirely, all modulation and automation assignments are also removed. Specifically to modulators, if the removed assignments are the only ones of a certain one (i.e., if the modulator is not assigned to other targets as well), the modulator itself is also removed.

  • When changing a slot's effect subtype (only applies to filters), everything is left unchanged. It is accepted that in certain cases, one may end up with "orphaned" modulation assignments as it is the case right now; e.g., when having modulation assigned to a parameter that is no longer available, like Resonance or Gain.

Changing Modulator Subtypes

Using the same commands described above, one can also change the subtype of internal modulators. Specifically, one could switch between envelope types (AHDSR, Flex and DBD), or LFO types (Rectangle, Triangle, Sawtooth, Random and Multi). A modulator cannot be inserted or removed. Its Type (LFO, Envelope, Step Modulator, Envelope Follower and Glide) cannot be changed either.

Special Cases

There are two effect types that cannot be set from KSP:

  • Surround Panner

  • AET filter

The Advanced Engine Tab

The Advanced Engine tab can be a useful tool for debugging and measuring the performance of your scripts.

While the Engine tab (a sub-tab of the Expert tab in the Browser Pane) can provide a useful display of performance statistics, the advanced version gives higher accuracy to things like CPU usage, and also displays information on multiple instances of KONTAKT when it is used as a plug-in.

Displaying the Advanced Engine Tab 

As mentioned earlier, the Engine tab is a sub section of the Expert tab, which can be found in the Browser Pane.

  • To access the Advanced Engine tab, hold the [Alt] (Windows) or [Opt] (macOS) key while clicking on the Engine tab.

  • To return to the main Engine tab, just click on the Engine tab again with no keys held.

Instance Overview

If you are running multiple instances of KONTAKT as a plug-in in a DAW or host, each instance will be given an entry in this section. If you are using KONTAKT in standalone, only the current instance will be displayed.

There are five performance statistics you can view here:

  • CPU: displays the current CPU load in percent (at a higher resolution than the other CPU readouts in KONTAKT) as well as the highest recorded peak CPU level (displayed in parenthesis). You can reset the high peak by re-initializing the KONTAKT instance by clicking on the Engine Restart (!) button.

  • Voices: displays the total number of voices currently in use by the KONTAKT instance.

  • Voices killed: displays the total number of voices that have been killed due to CPU overload (displayed on the left) and DFD overload (displayed on the right).

  • Process Buffer: displays the current audio buffer size in samples.

  • Events: displays the total number of events currently in the event queue. While a voice is the equivalent to a sample being played back, an event is more closely related to MIDI note messages being processed by the engine. For example, a single event could produce 3 voices, if there are 3 samples mapped to a single note. Additionally, if you are holding a MIDI key event though the triggered sample has finished playback, the voice will terminate, but the event will remain in the queue. As such, this display can be useful for tracking down events that are hanging, as these are not always audible in the way that hanging voices would be.

Total

The lower section displays the total performance statistics for all KONTAKT instances currently loaded. It has the following parameters: 

  • Voices and Voices killed: like the displays in the Instance Overview, but a total for all instances.

  • DFD load: if you are playing Instruments that use DFD mode, this measures their hard disk access. It is essentially a more accurate version of the Disk meter in KONTAKT’s Main Header.

  • DFD memory: a measurement of how much RAM is being used to process the DFD stream.

  • DFD requests: the total number of requests made by KONTAKT to read data from the hard disk.