Language Structure
Comming from other languages, the structure of cksp may feel unusual at first, especially since it is build on top of KSP, keeping some of the unusual aspects of its syntax and execution model while adding features from modern languages. That is why this chapter is designed to give you a quick overview of the language structure and syntax, covering the core constructs of CKSP, including callbacks, variables, functions, control flow, custom data structures, imports, namespaces, and pragma directives.
Callbacks and Event-Driven Programming
As CKSP is based on KSP, it is an event-driven language. A script does not start in a main function, but reacts to Kontakt events through callbacks. The primary entry point is usually on init, which runs once after compilation or engine restart and is typically used for declarations, UI setup, and initial state. All other callback types are triggered by specific events such as incoming MIDI data, UI interaction, timers, or asynchronous completion signals.
| Minimal CKSP Script Structure | |
|---|---|
Syntax Fundamentals
Like KSP, cksp is line-oriented. In practice, one statement is written per line and semicolons are not required. Indentation is used for readability, not for defining scope. Blocks are opened with a keyword and closed with end <keyword>, for example if ... end if, while ... end while, or on ... end on.
Assignments use :=, equality checks use =, and inequality checks use #. User-defined names must be declared before use. Multiline formatting is supported for function calls and initializers, which allows code to stay readable in larger expressions.
If a statement or expression must continue on the next line, use the continuation marker <...> at the end of the current line.
Execution Model
For each incoming event, Kontakt first sets the relevant built-ins for the active context, such as $EVENT_NOTE, $EVENT_VELOCITY, or $NI_UI_ID, and then invokes the matching callback. A callback runs sequentially until it reaches end on, exits early via exit, or pauses on wait().
The wait() case is essential for understanding runtime behavior. While one callback instance is paused, other callback instances may run. This is why scope rules and thread-safe local declarations matter, especially in combination with #pragma max_callback_depth(...).
Core Language Constructs
Callbacks
Callbacks define the top-level structure of a script and always follow the on <callback_name> ... end on pattern. Most projects begin with on init and then add only the callback types they need, for example on note, on release, on controller, on ui_controls, on ui_control(<widget>), on persistence_changed, on listener, and on async_complete.
exit can be used to stop a callback early. stop_wait(<callback_id>, <parameter>) can be used to cancel a pending wait.
Variables, Arrays, and Declarations
CKSP provides primitive types such as int, real, string, and bool, as well as composite types such as arrays and multidimensional arrays (int[], real[][], ...). Pointer variables for user-defined structures are supported as well. Global state is commonly declared in on init, while local state can be introduced directly in the function, loop, or callback scope where it is needed.
Immutable values can be declared with const. In larger scripts, separating declaration, assignment, and scope decisions early helps keep data flow and state lifetime predictable.
-
Variables and Arrays
Types, naming rules, arrays, multidimensional arrays, and indexing behavior.
-
Declarations
Declaration syntax, type annotations, constants, and persistent declarations.
-
Assignments
Assignment forms for variables, arrays, multidimensional arrays, and compound operators.
-
Lexical Scope
Local/global scope rules and thread-safety behavior in asynchronous callbacks.
Functions
Functions are defined in global scope and can be called from callbacks or other functions. Return values are produced via return. Parameter passing is by value by default. Reference semantics can be enabled per parameter with ref, or project-wide through #pragma pass_by("reference").
-
Functions
Function signatures, return values, parameter passing, and
refsemantics.
Control Flow and Loops
Branching is expressed with if / else if / else, select / case / default, and ternary expressions (condition ? a : b). Iteration is expressed with for, for ... in, and while. Loop flow can be controlled using break and continue.
-
Control Flow
Conditional expressions and branching with
if,select, and ternary syntax. -
Loops
for,for-each, andwhileloops, includingbreakandcontinue.
Custom Data Structures
struct allows defining custom data types with fields and methods. They are placed in the global scope, outside of callbacks or functions. While not as fully featured as classes in object-oriented languages, they provide a way to group related data and behavior together, which is especially useful for more complex projects.
Instances are handled through pointer semantics and can be created with new. Constructors (__init__) and, experimentally, operator overloading are supported.
struct Note
declare pitch: int
declare velocity: int
function play(self): int
return play_note(self.pitch, self.velocity, 0, -1)
end function
end struct
-
Custom Data Structures
Structs, methods, constructors, pointer semantics, and experimental overloading.
Imports and Namespaces
For modular code organization, files can be included with import. Paths can be resolved relative to the current file or to the project root. Namespaces organize global declarations and reduce naming collisions in larger projects.
-
Import Statements
Path resolution, root-relative imports, and import usage inside constructs.
-
Namespaces
Name organization, nested namespaces, lookup order, and shadowing behavior.
Pragma Directives
Pragmas define project-wide compiler behavior directly in source files. In practice, the most relevant directives are #pragma output_path(...), #pragma optimize(...), #pragma pass_by(...), #pragma combine_callbacks(...), and #pragma max_callback_depth(...).
-
Pragma Directives
Project-wide compiler directives for output, optimization, callback handling, and pass-by settings.
Fast Start Guidance
A practical way to start is to define shared state and UI in on init, implement only the callbacks needed for the current behavior, and move reusable logic into global functions early. Prefer local declarations to avoid cross-instance side effects. As a script grows, import, namespace and struct are the main tools for keeping structure maintainable.