diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 8040713..638fa21 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -4,6 +4,11 @@ "name": "Native Instruments" }, "plugins": [ + { + "name": "komplete-script", + "source": "./plugins/komplete-script", + "description": "Skills for developing with Komplete Script (kscript)" + }, { "name": "kontakt-developer", "source": "./plugins/kontakt-developer", diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 88b051b..c2bb63a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,4 @@ # Code owners. See https://docs.github.com/articles/about-code-owners -/plugins/kontakt-developer/ @ni-mgotsch +/plugins/kontakt-developer/ @ni-mgotsch @ni-acasapu +/plugins/komplete-script/ @ni-mgotsch @ni-acasapu diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f854e69..2444f73 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,6 +28,20 @@ user-visible change. 3. Register new plugins in `.claude-plugin/marketplace.json`. 4. Commit. That's the release. +## Plugins that depend on other plugins + +`kontakt-developer` builds on `komplete-script`: the Kontakt skill covers only the Kontakt +layer and loads the language skill for kscript itself. There is no dependency field in +`plugin.json`, and — because no plugin declares a `version` — no way to pin one either. So a +cross-plugin dependency is expressed two ways: + +1. Documented in both `README.md` files as a prerequisite. +2. Asserted at skill load time: the depending skill instructs loading the other skill first, + and tells the user which `/plugin install` to run if it is missing. + +Keep both in sync when adding a dependency, and never duplicate reference material across +plugins — one source of truth, referenced by name. + ## Documenting commands Command args live in the command file's frontmatter (`argument-hint`) and are diff --git a/README.md b/README.md index 34e6717..07ae73f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,11 @@ Claude plugins provided by Native Instruments. ## Plugins -- **kontakt-developer** — Contains skills for developing Kontakt instruments and tools +- **komplete-script** — The Komplete Script (kscript) language: syntax, standard library, + `ui` package. Host-independent. +- **kontakt-developer** — Kontakt instruments and tools: KSP binding, the `kontakt` package, + Kontakt Controls, the Kontakt MCP dev loop. **Builds on `komplete-script`** — install both + for Kontakt UI work. ## Install the marketplace @@ -23,6 +27,7 @@ Or from remote by URL instead: ## Install a plugin ``` +/plugin install komplete-script@native-instruments /plugin install kontakt-developer@native-instruments ``` diff --git a/plugins/komplete-script/.claude-plugin/plugin.json b/plugins/komplete-script/.claude-plugin/plugin.json new file mode 100644 index 0000000..5ea76d2 --- /dev/null +++ b/plugins/komplete-script/.claude-plugin/plugin.json @@ -0,0 +1,7 @@ +{ + "name": "komplete-script", + "description": "Skills for developing with Komplete Script (kscript)", + "author": { + "name": "Native Instruments" + } +} diff --git a/plugins/komplete-script/README.md b/plugins/komplete-script/README.md new file mode 100644 index 0000000..e0f6cfd --- /dev/null +++ b/plugins/komplete-script/README.md @@ -0,0 +1,31 @@ +# komplete-script + +Claude Code plugin for developing with **Komplete Script** (kscript) — the language behind +Komplete UI. + +## Install + +``` +/plugin install komplete-script@native-instruments +``` + +See the [marketplace README](../../README.md) for adding the marketplace first. + +## Skills + +Skills load automatically when relevant; no command needed. + +### `language` + +The kscript language itself: syntax, types, classes, components, templates, modifiers, +modules, reactivity, layout, gestures, the standard library, and the `ui`, `math`, `path`, +`uri` and `audio_components` packages. kscript is **not in the model's training data** — the +skill bundles the full reference and loads it on demand before any kscript is written. + +Triggers when you work with `.kscript` files or mention kscript, Komplete Script or +Komplete UI. + +The skill is **host-independent**. Building a Kontakt instrument UI additionally needs +[`kontakt-developer`](../kontakt-developer/README.md), which adds KSP binding, the `kontakt` +package, Kontakt Controls, the Kontakt MCP dev loop, and the Kontakt → kscript version +mapping. diff --git a/plugins/komplete-script/skills/language/SKILL.md b/plugins/komplete-script/skills/language/SKILL.md new file mode 100644 index 0000000..92728ec --- /dev/null +++ b/plugins/komplete-script/skills/language/SKILL.md @@ -0,0 +1,60 @@ +--- +name: language +description: Write Komplete Script (kscript) — the language itself. Covers syntax, types, classes, components, templates, modifiers, modules, reactivity, layout, gestures, the standard library and the ui/math/path/uri/audio_components packages. Use when working with .kscript files or when the user mentions kscript, Komplete Script or Komplete UI. kscript is a proprietary language NOT in AI training data — always load the bundled references before writing any kscript code. Host-independent; for Kontakt instrument UIs (KSP binding, the kontakt package, Kontakt Controls, the Kontakt MCP dev loop) also load the kontakt-developer komplete-script skill. +--- + +# Komplete Script (kscript) + +kscript is a statically typed language with a declarative, reactive UI layer (Komplete UI). +It is **not in your training data** — never write kscript from intuition or by analogy to +Swift/SwiftUI/JavaScript. Ground every construct in the bundled references. + +This skill covers the language and its bundled packages only. Anything host-specific — how +a host loads a program, where files must live on disk, how the UI binds to an audio engine, +how compile output is read back — lives in that host's own skill. + +For Kontakt instruments that means `kontakt-developer:komplete-script` — KSP binding, the +`kontakt` package, Kontakt Controls, the resource-container layout and the Kontakt MCP dev +loop. Load it alongside this skill for any Kontakt work. + +## Reference files — load before writing code + +Read these on demand from this skill's `references/` directory: + +| File | When to read | +|---|---| +| `references/language.md` | **Always**, before writing any kscript. Syntax, types, variables, functions, classes, components, templates, modifiers, modules, declarative UI, reactivity, layout, gestures. | +| `references/stdlib.md` | Using built-in types: Int, Float, String, Array, Map, Color, Range, Angle, print, Error/Warning. | +| `references/ui-package.md` | Building UI: Components (Text, Rectangle, ZStack, …), Modifiers, enums, constants from the `ui` package. | +| `references/other-packages.md` | `math`, `uri`, `path` and `audio_components` packages. | +| `references/versions.md` | Checking whether a feature exists in the target language version. | +| `references/troubleshooting.md` | Any error/warning, unexpected behavior (flickering, state resets, cycle errors), known issues. | + +For a typical UI task the set is `language.md` + `ui-package.md` (+ `stdlib.md` for +collection or string work). + +## Language-level project conventions + +- One file = one **module**. Filenames must be lowercase (letters, digits, underscores). +- Import paths are dot-separated and **always resolved from the project root** — the host's + script directory — never relative to the importing file. +- The main module must export the entry point: `export var main: Component = `. +- Exported types must not refer to un-exported types; annotate the export where needed. + +## Version discipline + +Features are gated by **language version** (`references/versions.md`). A host maps its own +release to a language version — for Kontakt, `kontakt-developer`'s +`references/kontakt-versions.md` holds that mapping and `kontaktTargetVersion` sets the +ceiling. Establish the target language version before writing code, and never use a feature +newer than it. + +## Hard rules + +1. **Never invent API.** Every component, modifier, function, parameter label and enum case + you write must appear in a reference file. If it is not there, say so rather than guessing. +2. The main module must `export var main = ` (type-annotated when required). +3. Respect the target language version — check `references/versions.md` when unsure. +4. Verify code through the host's feedback loop (compile errors, warnings, `print` output) + before declaring success. Use `print("...")` for temporary debug logging and remove it + before finishing. diff --git a/plugins/kontakt-developer/skills/komplete-script/references/language.md b/plugins/komplete-script/skills/language/references/language.md similarity index 91% rename from plugins/kontakt-developer/skills/komplete-script/references/language.md rename to plugins/komplete-script/skills/language/references/language.md index 6421c8e..6bae679 100644 --- a/plugins/kontakt-developer/skills/komplete-script/references/language.md +++ b/plugins/komplete-script/skills/language/references/language.md @@ -2,9 +2,9 @@ # Komplete Script (kscript) Language Reference -Komplete Script is the language for building Kontakt instrument UIs and their business logic. It is **type-safe and statically typed**. Business logic is written imperatively; the UI is **declarative and reactive** — you describe what the UI looks like based on state, and the runtime keeps it in sync automatically. It works alongside KSP (Kontakt Script Processor), which handles real-time MIDI/audio; Komplete Script sits on top. +Komplete Script is the language for building Komplete UI interfaces and their business logic. It is **type-safe and statically typed**. Business logic is written imperatively; the UI is **declarative and reactive** — you describe what the UI looks like based on state, and the runtime keeps it in sync automatically. The language itself is host-independent; a host embeds it and supplies the real-time audio/MIDI layer underneath. -Files use the `.kscript` extension. Each file is a **module**. The entry point of an instrument is an exported `main` variable of type `Component`: +Files use the `.kscript` extension. Each file is a **module**. The entry point of a Komplete Script program is the main module's exported `main` variable of type `Component`: ```kscript import { Text } from ui @@ -12,7 +12,7 @@ import { Text } from ui export var main: Component = Text("Hello, world!") ``` -`print(...)` is always available without import (output goes to Creator Tools). +`print(...)` is always available without import (output goes to the host's log channel). ## Comments @@ -411,7 +411,7 @@ class BoundingBox { - Multiple constructors are allowed if their parameter signatures differ. - Constructor parameters follow function parameter rules (labels, `_`, defaults). -### Method Overloading (Kontakt 8.12) +### Method Overloading (kscript 1.9) A class can define multiple methods with the same name, as long as their parameters differ — in number, in types, or in argument labels. The compiler selects the matching overload based on the arguments at the call site: @@ -439,7 +439,7 @@ Methods that differ only in their return type are not valid overloads. Overloadi Interpolating an instance prints its stored properties: `Vec2(x: 3.0, y: 4.0)` (computed properties excluded). Define `to_string() -> (String)` to customize. -### Subscript Operator (Kontakt 8.11) +### Subscript Operator (kscript 1.8) Define a method named `at` with exactly one parameter to enable `obj[expr]` reads; define `assign` with exactly two parameters (value first, then subscript) and no return value to enable `obj[expr] = value`. Both are also callable directly as normal methods. @@ -695,7 +695,7 @@ A component body may produce multiple sibling components (directly, or via decla ### Exporting -`export component Name { … }` exports a component. The instrument entry point is `export var main: Component = SomeComponent()`. +`export component Name { … }` exports a component. The program entry point is `export var main: Component = SomeComponent()`. ## Templates @@ -874,11 +874,11 @@ import { controls.knob.Knob as Knob } from my_module ### Directory Structure -Modules may live in subdirectories; import paths are dot-separated and **always resolved from the project root** (the `komplete_scripts` directory), never relative to the importing file: +Modules may live in subdirectories; import paths are dot-separated and **always resolved from the project root** (the host's script directory — `komplete_scripts/` in Kontakt), never relative to the importing file: ```kscript import * from components.tabbar -import * from kontakt_components.base.button_base +import * from shared.base.button_base ``` ### Folder Modules @@ -937,7 +937,7 @@ export component Checkbox { ### Reactivity Pitfalls 1. **`var` in components does not work** — a component reading a global `var` in an expression will never update when the `var` changes. Use component state or a class property instead. -2. **UI code must be side-effect free.** The order and count of re-evaluations is NOT guaranteed and may change between Kontakt versions. Never mutate anything (including globals) inside computed state/properties. +2. **UI code must be side-effect free.** The order and count of re-evaluations is NOT guaranteed and may change between language versions. Never mutate anything (including globals) inside computed state/properties. 3. **Never create components from functions** (`(Args) -> (Component)` properties or method calls in the body) — the component is recreated whenever any argument changes. Use `Template(…)` instead. ## Layout Fundamentals @@ -1058,46 +1058,6 @@ With a tap and a drag on the same component: a pointer-down triggers the tap's ` `DragGesture(minimum_distance: 0, …)` normally starts on pointer-down, **except** when a higher-priority tap gesture exists — then the drag starts on first movement so the tap can still fire. If the zero-threshold drag is listed first (higher priority), the tap never triggers. -## Kontakt Integration Pattern - -Connect to KSP controls via the `kontakt` package. KSP connections are fixed at load time, so declare them **globally**, not inside a component: - -```kscript -import { VStack, Text, Arc, DragGesture, Padding } from ui -import { KSPKnob } from kontakt - -var reverb_knob = KSPKnob(id: "reverb") // connects to KSP ui_knob "reverb" - -component ReverbSend { - accumulated_delta: Float = 0.0 // carries sub-integer drag remainder - - VStack(spacing: 4) { - Arc( - color: Color(0xFF000000), - start_angle: Angle(degrees: 135), - angle: Angle(degrees: reverb_knob.normalized_value * 270), - ) with { - DragGesture(fun (event) { - var dy = event.delta.y / event.frame.height - self.accumulated_delta = self.accumulated_delta - dy * (reverb_knob.max - reverb_knob.min) - var steps = self.accumulated_delta.to_int() - if steps != 0 { - reverb_knob.value = (reverb_knob.value + steps).clamped(min: reverb_knob.min, max: reverb_knob.max) - self.accumulated_delta = self.accumulated_delta - steps - } - }) - } - Text(reverb_knob.label) // reactive: updates with KSP label - Text("\{reverb_knob.value}") // reactive: updates with KSP value - } with { - Padding(16) - } -} - -export var main: Component = ReverbSend() -``` - -`KSPKnob` exposes reactive `value` (Int), `min`, `max`, `normalized_value` (Float 0.0–1.0), and `label`. The `kontakt controls` package provides ready-made `Knob`/`Slider` components wrapping this pattern. Colors are constructed as `Color(0xAARRGGBB)` (e.g. `Color(0xFFFF0000)` opaque red). ## Gotchas (differences from mainstream languages) @@ -1123,13 +1083,12 @@ export var main: Component = ReverbSend() 20. **UI code must be side-effect free** — re-evaluation order/count is unspecified; any value read in a reactive expression becomes a tracked dependency (even reads inside `print`). 21. **Map lookups always return optionals; assigning `nil` deletes the key.** Force unwrap (`!`) on `nil` is a runtime crash. 22. **Empty collection literals need type annotations**; empty map is `[:]`, not `{}` or `[]`. -23. **Import paths resolve from the project root** (`komplete_scripts/`), never relative to the current file. Module filenames must be lowercase (letters, digits, underscores). Nested-namespace imports require `as` renames. +23. **Import paths resolve from the project root** (the host's script directory), never relative to the current file. Module filenames must be lowercase (letters, digits, underscores). Nested-namespace imports require `as` renames. 24. **Block comments nest.** 25. **`===` (identity) exists only for class instances**; `==` compares values. -26. **KSP connections (e.g. `KSPKnob`) must be declared globally** — they are fixed at load time and don't belong inside components. -27. **Higher (later) siblings block lower siblings' gestures entirely** — you cannot combine gestures across siblings. -28. Float literals need digits on both sides of the dot (`0.5`, not `.5`; `1.0`, not `1.`). -29. **Case is enforced**: symbols (variables, properties, functions, enum cases) must start lowercase; types (classes, components, modifiers, enums, aliases) must start uppercase. `var TITLE = ""` does not compile. -30. **No direct chaining onto a constructor call** — `Color(0xFFFFFFFF).opacity(0.5)` is illegal; write `(Color(0xFFFFFFFF)).opacity(0.5)` or bind to a variable first. -31. **Member order is enforced.** Classes: properties → constructors → methods. Components/modifiers: properties/bindings → constructors → state/methods → child components last. -32. **No `var` inside templates or trailing `{ … }` children blocks** — those take component expressions only. Pass values in via template parameters, properties, or state. +26. **Higher (later) siblings block lower siblings' gestures entirely** — you cannot combine gestures across siblings. +27. Float literals need digits on both sides of the dot (`0.5`, not `.5`; `1.0`, not `1.`). +28. **Case is enforced**: symbols (variables, properties, functions, enum cases) must start lowercase; types (classes, components, modifiers, enums, aliases) must start uppercase. `var TITLE = ""` does not compile. +29. **No direct chaining onto a constructor call** — `Color(0xFFFFFFFF).opacity(0.5)` is illegal; write `(Color(0xFFFFFFFF)).opacity(0.5)` or bind to a variable first. +30. **Member order is enforced.** Classes: properties → constructors → methods. Components/modifiers: properties/bindings → constructors → state/methods → child components last. +31. **No `var` inside templates or trailing `{ … }` children blocks** — those take component expressions only. Pass values in via template parameters, properties, or state. diff --git a/plugins/kontakt-developer/skills/komplete-script/references/other-packages.md b/plugins/komplete-script/skills/language/references/other-packages.md similarity index 93% rename from plugins/kontakt-developer/skills/komplete-script/references/other-packages.md rename to plugins/komplete-script/skills/language/references/other-packages.md index eea6565..9c962d5 100644 --- a/plugins/kontakt-developer/skills/komplete-script/references/other-packages.md +++ b/plugins/komplete-script/skills/language/references/other-packages.md @@ -53,7 +53,7 @@ var arctangent = atan(1.5) // arctangent equals 0.98279372324733 #### atan2(y: Float, x: Float) -> (Float) -Arc tangent of y / x using argument signs for quadrant. Since Kontakt 8.10. +Arc tangent of y / x using argument signs for quadrant. Since kscript 1.5. ```kscript var arctangent2 = atan2(y: 7, x: 0) // arctangent2 equals 1.5708 @@ -139,7 +139,7 @@ var radical = sqrt(4.0) // radical equals 2.0 # URI -Kontakt 8.12. +kscript 1.9. ```kscript import * from uri @@ -233,7 +233,7 @@ export var main = Main() # Path -Kontakt 8.12. Package renamed `fs` → `path`. +kscript 1.9. Package renamed `fs` → `path`. ```kscript import * from path @@ -293,7 +293,7 @@ Properties (all get-only; component getters return empty path if absent): - `extension: Path` — file extension including leading dot (e.g. `.wav`). - `has_parent`, `has_root_name`, `has_root`, `has_filename`, `has_stem`, `has_extension`, `is_absolute`, `is_relative`, `is_empty`: Bool. -Methods (`appending` is overloaded, replacing the old `appending_all`/`appending_path` — Kontakt 8.12): +Methods (`appending` is overloaded, replacing the old `appending_all`/`appending_path` — kscript 1.9): - `appending(_ path: String) -> (Path)` — append string as new path component. - `appending(_ other: Path) -> (Path)` — append `other`'s components. - `appending(_ segments: [Path]) -> (Path)` — append all segments from the array in order. @@ -317,7 +317,7 @@ Parameter: `_ uri: URI` (required). # Audio Components -Kontakt 8.12. +kscript 1.9. ```kscript import * from audio_components @@ -325,7 +325,7 @@ import * from audio_components ## class VisibleRange -Half-open range of samples within an audio sample: [begin, end). Floating point for sub-sample precision. Renamed from `SampleRange` in Kontakt 8.12. +Half-open range of samples within an audio sample: [begin, end). Floating point for sub-sample precision. Renamed from `SampleRange` in kscript 1.9. ```kscript export class VisibleRange { @@ -363,15 +363,15 @@ Constructor parameters: ```kscript import { Waveform } from audio_components -import { load_sample, library_path } from kontakt -export var main = Waveform(sample: load_sample(library_path.appending("Samples").appending("my_sample.wav"))!, -) +// `my_sample` stands in for a concrete Sample obtained from the host — the +// loading function is host API, not part of `audio_components`. +export var main = Waveform(sample: my_sample) ``` ## interface Sample -Audio sample displayable by `Waveform`. No constructor — use `kontakt.Sample` (concrete implementation), obtained via functions such as `load_sample`. +Audio sample displayable by `Waveform`. This is an **interface only** — it has no constructor. The concrete implementation and the function that loads it are supplied by the host (in Kontakt: `kontakt.Sample` via `load_sample` — see that plugin's `kontakt-package.md`). ```kscript export interface Sample { diff --git a/plugins/kontakt-developer/skills/komplete-script/references/stdlib.md b/plugins/komplete-script/skills/language/references/stdlib.md similarity index 93% rename from plugins/kontakt-developer/skills/komplete-script/references/stdlib.md rename to plugins/komplete-script/skills/language/references/stdlib.md index dc67d62..1302277 100644 --- a/plugins/kontakt-developer/skills/komplete-script/references/stdlib.md +++ b/plugins/komplete-script/skills/language/references/stdlib.md @@ -8,10 +8,10 @@ fun print(_ message: String) -> () ``` -Prints a debug message (can be read from MCP). +Prints a debug message (readable through the host's message channel). ```kscript -print("Hello, KSP console!") +print("Hello, world!") ``` ## Warning @@ -20,7 +20,7 @@ print("Hello, KSP console!") fun warning(_ message: String) -> () ``` -Prints a warning message (can be read from MCP). +Prints a warning message (readable through the host's message channel). ```kscript warning("Be careful") @@ -32,7 +32,7 @@ warning("Be careful") fun error(_ message: String) -> () ``` -Aborts the current call stack and prints an error message to Creator Tools. Code after `error()` in the aborted stack is not reached. +Aborts the current call stack and prints an error message to the host's log channel. Code after `error()` in the aborted stack is not reached. ```kscript if amount > balance { @@ -81,7 +81,7 @@ print("\{value.formatted(digits: 2)}") // Prints "3.14" A collection of unicode characters. All strings are expected to be UTF-8 encoded. - Compare with `==` / `!=`. -- Escape sequences: `\n`, `\r` (since 8.9), `\\`, `\"`, `\u{030A}` (unicode codepoint, hex), `\{}` (interpolation). +- Escape sequences: `\n`, `\r` (kscript 1.5), `\\`, `\"`, `\u{030A}` (unicode codepoint, hex), `\{}` (interpolation). - Combining codepoints (e.g. `\u{030A}`) merge with the leading character and do not count as separate characters for `length`. Properties: @@ -139,7 +139,7 @@ Properties: Methods: - `all_satisfy(_ predicate: (Element) -> (Bool)) -> (Bool)` — returns `true` on an empty array. - `append(_ element: Element)` — appends a single element. -- `append(_ other: [Element])` — appends all elements from another array (Kontakt 8.12; replaces `append_all`, removed in 8.12). +- `append(_ other: [Element])` — appends all elements from another array (kscript 1.9; replaces `append_all`, removed in 1.9). - `clear()` - `contains(_ el: Element) -> (Bool)` — requires `Element` supports `==`. - `copy() -> ([Element])` @@ -171,7 +171,7 @@ Collection of key-value pairs: `[Key: Value]`. - Add/update: `map[key] = value`. Remove: `map[key] = nil` (removing a non-existing key is a no-op). - Iterable with `for key, value in map { ... }` and interpolatable into strings. - Gotcha: iteration and print order are **non-deterministic**. -- Supported key types: `Int`, `String`, `Float`, `Bool`, enumerations (since 8.5). +- Supported key types: `Int`, `String`, `Float`, `Bool`, enumerations (kscript 1.2). ```kscript var phone_book: [String: String] = ["Malcom": "012345", "Jane": "789078"] diff --git a/plugins/komplete-script/skills/language/references/troubleshooting.md b/plugins/komplete-script/skills/language/references/troubleshooting.md new file mode 100644 index 0000000..189b9a3 --- /dev/null +++ b/plugins/komplete-script/skills/language/references/troubleshooting.md @@ -0,0 +1,65 @@ + + +# Troubleshooting Reference + +## Common Errors + +### Error: `unwrapping a nil value` + +Cause: `!` force-unwrap on an optional that was `nil` at runtime (e.g. map lookup for a missing key returns nil). + +```kscript +var values = ["a": 0, "b": 1, "c": 2] +var fails = values["missing_key"]! // error — map lookup returns nil +``` + +Fix: check for `nil` before unwrapping, or use a ternary fallback: + +```kscript +var result = values["missing_key"] != nil ? values["missing_key"]! : -1 + +if values["missing_key"] != nil { + print("Found: \{values["missing_key"]!}") +} +``` + +### Components recreated / flickering / state resets (creating components inside a function) + +Symptom: components are recreated repeatedly, causing flickering or unexpected state resets. No error message. + +Cause: calling a function inside a reactive expression (property, computed state, component body) tracks the function's *arguments* as dependencies. When arguments change, the function reruns — if it creates components, new instances replace old ones. + +```kscript +fun make_item(text: String) -> (Component) { + return Text(text) // new Text created every time text changes +} +``` + +Fix: use a `template` instead — templates forward parameters lazily and create no reactive dependency on their arguments: + +```kscript +var make_item = template (text: String) { + Text(text) // stable — not recreated when text changes +} +``` + +## Known Issues + +- **Differently composed UTF-8 characters may not compare equal.** Pre-composed vs decomposed forms look identical but can compare unequal in string comparison. (Several string methods were fixed in kscript 1.0; comparison itself remains a known issue.) +- **Spacer size incorrect if modifiers are applied to it.** A modifier applied to a `Spacer` in a stack can alter its size unexpectedly. Workaround: avoid applying modifiers directly on `Spacer`. +- **Indexing the iterated container inside a declarative for loop** (fixed in kscript 1.1): accessing `presets[i]` inside `for i, preset in presets.enumerated() { ... }` can cause out-of-bounds errors when elements are removed. Workaround (pre-1.1): use only the iteration values (`preset`), not `presets[i]`. +- **Same state in a declarative for loop's sequence and its body → "cycle detected" error** (fixed in kscript 1.1): e.g. using `self.offset` both in `presets.subsequence(from: self.offset, length: 1)` and in the loop body. Workaround (pre-1.1): embed the total index/id into the data itself and avoid using the state inside the body: + +```kscript +class Preset { + id: Int + name: String +} +// iterate: for preset in presets.subsequence(from: self.offset, length: 1) { Text("\{preset.id}: \{preset.name}") } +``` + +## FAQ + +- **Komplete UI vs Komplete Script?** Komplete UI = the UI framework (the `ui` package). Komplete Script = the underlying programming language used in `.kscript` files (usable for general scripting too). +- **Reporting issues?** Join the NI Developer Slack (invite via builder-experience-team@native-instruments.com). + diff --git a/plugins/kontakt-developer/skills/komplete-script/references/ui-package.md b/plugins/komplete-script/skills/language/references/ui-package.md similarity index 97% rename from plugins/kontakt-developer/skills/komplete-script/references/ui-package.md rename to plugins/komplete-script/skills/language/references/ui-package.md index b74571c..e1f1f80 100644 --- a/plugins/kontakt-developer/skills/komplete-script/references/ui-package.md +++ b/plugins/komplete-script/skills/language/references/ui-package.md @@ -113,14 +113,14 @@ export var main = HStack { ### Text `Text(_ text: String, color: Color? = nil, size: Int? = nil, font_family: FontFamilyName? = nil, italic: Bool = false, font_weight: Int = font_weights.normal, letter_spacing: Float? = nil, line_height: Float? = nil, line_limit: Int? = nil, multiline_alignment: HorizontalAlignment? = nil)` -Displays text. `nil` params inherit from environment; root env defaults: color black, size 16, font Roboto, letter spacing 0, line height multiplier 1, line limit max_integer, multiline alignment center. `letter_spacing` (px, can be negative) and `line_height` (multiplier, e.g. 1.5 = 150%) introduced in Kontakt 8.11. Layout: exact space needed; wraps vertically if too narrow; elides as last resort; never uses more space than needed. +Displays text. `nil` params inherit from environment; root env defaults: color black, size 16, font Roboto, letter spacing 0, line height multiplier 1, line limit max_integer, multiline alignment center. `letter_spacing` (px, can be negative) and `line_height` (multiplier, e.g. 1.5 = 150%) introduced in kscript 1.8. Layout: exact space needed; wraps vertically if too narrow; elides as last resort; never uses more space than needed. ```kscript import { Text } from ui export var main = Text("Hello, world!") ``` ### TextInput -Single line of editable text (introduced Kontakt 8.4). Only offers text, cursor, selection — style it with other modifiers/components. Tapping in acquires keyboard focus, tapping outside removes it. Layout: fills available horizontal space; fixed height from font family & size. +Single line of editable text (introduced in kscript 1.1). Only offers text, cursor, selection — style it with other modifiers/components. Tapping in acquires keyboard focus, tapping outside removes it. Layout: fills available horizontal space; fixed height from font family & size. Constructor with binding (state reflects edits immediately): ```kscript @@ -135,7 +135,7 @@ TextInput( italic: Bool = false, font_weight: Int = font_weights.normal, alignment: HorizontalAlignment = HorizontalAlignment.left, - letter_spacing: Float? = nil, // env default: 0; Kontakt 8.11 + letter_spacing: Float? = nil, // env default: 0; kscript 1.8 on_focus_changed: (Bool) -> () = fun (focused) {}, on_editing_finished: () -> () = fun () {}, // focus lost or enter/return on_submitted: () -> () = fun () {}, // enter/return @@ -331,7 +331,7 @@ export var main: Component = HoverText() ### LetterSpacing `LetterSpacing(_ spacing: Float?)` -Overrides letter spacing (px, can be negative) inherited by descendent text; `nil` inherits. Introduced in Kontakt 8.11. +Overrides letter spacing (px, can be negative) inherited by descendent text; `nil` inherits. Introduced in kscript 1.8. ```kscript import { LetterSpacing, Text } from ui export var main = Text("Wide") with { LetterSpacing(3.0) } @@ -339,7 +339,7 @@ export var main = Text("Wide") with { LetterSpacing(3.0) } ### LineHeight `LineHeight(_ factor: Float?)` -Overrides line height multiplier for descendent Text (e.g. 1.5 = 150% of natural line height); `nil` inherits. Introduced in Kontakt 8.11. +Overrides line height multiplier for descendent Text (e.g. 1.5 = 150% of natural line height); `nil` inherits. Introduced in kscript 1.8. ```kscript import { LineHeight, Text } from ui export var main = Text("Two\nlines") with { LineHeight(1.5) } @@ -411,7 +411,7 @@ Popover( ) ``` ```kscript -// Introduced with Kontakt 8.5.1 +// Introduced with kscript 1.3 Popover(visible: Bool, ...) // never auto-closes; mouse events pass through background ``` If the popover doesn't fit in `direction` it is mirrored, then repositioned to fit the app frame; leading/trailing alignment flips if it doesn't fit. Popover content has the size of the entire scene available; no layout effect on the anchor. diff --git a/plugins/komplete-script/skills/language/references/versions.md b/plugins/komplete-script/skills/language/references/versions.md new file mode 100644 index 0000000..6968c65 --- /dev/null +++ b/plugins/komplete-script/skills/language/references/versions.md @@ -0,0 +1,20 @@ + + +# Feature availability by kscript version + +Every entry below is a language, standard-library, `ui`, `math`, `path`, `uri` or +`audio_components` change. Host-specific additions are documented by the host's own plugin +(for Kontakt: `kontakt-developer` → `references/kontakt-versions.md`, which also maps +Kontakt releases to the language versions used here). + +Versions 1.4, 1.6 and 1.7 were not shipped in a public host release — no entries exist. + +| kscript | Notable additions / changes | +|---|---| +| 1.0 | Initial public language. `Map`, `Angle` (replaces Float angles in Rotation/Arc/Canvas), `Range`, `enumerated()`, `clamped`, type deduction for templates and function expressions, unicode code points in strings, named arguments in any order; text modifiers (`LineLimit`, `FontFamily`, `FontSize`, `MultilineTextAlignment`, `TextColor`); Popover fixes (initial visible, anchor following, reload crash). **Breaking:** `Array.filter` → `filtered(by:)`; `subsequence(count:)` → `length:`; `String.replace_all` → `replacing_all`; `String.removing` removed; if-else expression → ternary `a ? b : c`; `Ring` → `Arc`; font size is `Int`; `FontFamily` (class) → `FontFamilyName`; Math `clamp`/`clampf` and `degrees_to_radians`/`radians_to_degrees` removed; exported types must not refer to un-exported types (annotate e.g. `export var main: Component = Main()`); `TapGesture` cancel/up semantics changed | +| 1.1 | `TextInput` component; fixes for declarative `for`/`if` update-before-body and "cycle detected" errors | +| 1.2 | Stable `Drag`/`Drop` modifiers; enumerations as `Map` keys; compile-time improvements | +| 1.3 | `Popover` `visible: Bool` constructor parameter | +| 1.5 | Default function arguments (`fun f(p: Int = 0)`); `\r` escape in strings; `atan2` | +| 1.8 | `letter_spacing` (`Text`, `TextInput`) + `LetterSpacing` modifier; `line_height` (`Text`) + `LineHeight` modifier; faster number parsing; subscript operator (`at`/`assign`) for custom classes | +| 1.9 | Method overloading; `audio_components`, `path` and `uri` packages (`fs` renamed to `path`, `SampleRange` renamed to `VisibleRange`); `Array.append` overloaded to accept `[Element]`, replacing `append_all` | diff --git a/plugins/kontakt-developer/README.md b/plugins/kontakt-developer/README.md index 55b8db4..b305c0f 100644 --- a/plugins/kontakt-developer/README.md +++ b/plugins/kontakt-developer/README.md @@ -10,6 +10,18 @@ Claude Code plugin for developing Kontakt instruments and tools. See the [marketplace README](../../README.md) for adding the marketplace first. +## Prerequisite + +This plugin covers the **Kontakt layer** only. The kscript language itself lives in +[`komplete-script`](../komplete-script/README.md) — install it too: + +``` +/plugin install komplete-script@native-instruments +``` + +Without it this plugin's `komplete-script` skill has no language reference to load and will tell +you to install it rather than writing kscript. + ## Commands ### `/setup-mcp [port]` @@ -28,10 +40,12 @@ Skills load automatically when relevant; no command needed. ### `komplete-script` -Develop Kontakt instrument UIs and tools with **kscript** (Komplete UI) — a -proprietary declarative UI language that is **not in the model's training data**. -The skill bundles the full language reference and loads it on demand before any -kscript is written. +The Kontakt-specific layer of Komplete UI: KSP control binding, the built-in `kontakt` +package, the optional Kontakt Controls package, resource-container layout, +`kontaktTargetVersion` and its mapping to a kscript language version, and the Kontakt MCP +dev loop. + +Triggers when you build, edit or debug a Kontakt instrument UI, work on an `.nki` or its +`komplete_scripts` folder, or mention KSP / Kontakt Controls. -Triggers when you build, edit, or debug a Kontakt instrument UI, work with -`.kscript` files, or mention Komplete UI / kscript / Kontakt Controls / KSP. +It loads `komplete-script:language` first for the language itself. diff --git a/plugins/kontakt-developer/skills/komplete-script/SKILL.md b/plugins/kontakt-developer/skills/komplete-script/SKILL.md index b5ae599..46fe759 100644 --- a/plugins/kontakt-developer/skills/komplete-script/SKILL.md +++ b/plugins/kontakt-developer/skills/komplete-script/SKILL.md @@ -1,27 +1,42 @@ --- name: komplete-script -description: Develop Kontakt instrument UIs and tools with kscript (Komplete UI). Use when the user wants to build, edit, or debug a Kontakt instrument UI, works with .kscript files, mentions Komplete UI, kscript, Kontakt Controls, or asks to connect a UI to KSP. kscript is a proprietary language NOT in AI training data — always load the bundled references before writing any kscript code. +description: Build Kontakt instrument UIs with Komplete UI — the Kontakt layer on top of kscript. Covers KSP control binding, the kontakt package, the Kontakt Controls package, resource-container layout, kontaktTargetVersion, and the Kontakt MCP dev loop. Use when the user wants to build, edit or debug a Kontakt instrument UI, works on an .nki or its komplete_scripts folder, or asks to connect a UI to KSP. Requires the komplete-script language skill for the language itself — load it first. --- -# Komplete Script for Kontakt +# Komplete UI for Kontakt -kscript (Komplete Script) is a declarative UI language for building Kontakt instrument interfaces. It is **not in your training data** — never write kscript from intuition or by analogy to Swift/SwiftUI/JavaScript. Always ground every construct in the bundled references. +This skill covers the **Kontakt-specific** layer: how a kscript UI lives inside an +instrument, how it binds to KSP, and how to verify it through Kontakt's MCP server. -## Reference files — load before writing code +## Step 1 — load the language skill first + +The kscript language (syntax, types, components, modifiers, `ui` package, stdlib) is **not +in this skill**. Before writing any kscript, invoke the `komplete-script:language` +skill and read its references. + +If that skill is unavailable, tell the user to install it: + +``` +/plugin install komplete-script@native-instruments +``` + +Do **not** write kscript from memory — the language is not in your training data. + +## Reference files Read these on demand from this skill's `references/` directory: | File | When to read | |---|---| -| `references/language.md` | **Always**, before writing any kscript. Syntax, types, variables, functions, classes, components, templates, modifiers, modules, declarative UI, layout, gestures. | -| `references/stdlib.md` | Using built-in types: Int, Float, String, Array, Map, Color, Range, Angle, print, Error/Warning. | -| `references/ui-package.md` | Building UI: Components (Text, Rectangle, ZStack, …), Modifiers, enums, constants from the `ui` package. | -| `references/kontakt-package.md` | Connecting to Kontakt/KSP: KSP control classes (KSPSlider, KSPKnob, …), zones, samples, programs. Built into Kontakt — always available. | +| `references/kontakt-package.md` | Connecting to Kontakt/KSP: KSP control classes (KSPSlider, KSPKnob, …), Help modifier, zones, groups, samples, programs. Built into Kontakt — always available. | +| `references/kontakt-integration.md` | The binding pattern: how a component reads and writes a KSP control's value. | | `references/kontakt-controls.md` | Using the optional Kontakt Controls package (ready-made Slider, Knob, XYPad, Stepper, Switch, ToggleButton). See install note below. | -| `references/other-packages.md` | Math, URI, Filesystem, Audio Components packages. | -| `references/troubleshooting.md` | Any error/warning in logs, unexpected behavior, KSP→Komplete UI migration, feature availability by Kontakt version. | +| `references/kontakt-versions.md` | Kontakt → kscript version mapping, `kontaktTargetVersion`, Kontakt-only feature availability. | +| `references/ksp-migration.md` | Porting an existing KSP UI, or explaining Komplete UI to a KSP developer. | -For a task like "add a knob controlling filter cutoff", the typical set is: `language.md` + `ui-package.md` + `kontakt-package.md` (+ `kontakt-controls.md` if the package is installed). +For a task like "add a knob controlling filter cutoff": `komplete-script:language` → `language.md` + +`ui-package.md`, plus `kontakt-package.md` and `kontakt-integration.md` here (+ +`kontakt-controls.md` if the package is installed). ## Project structure @@ -37,18 +52,37 @@ Resources/ └── info/library.json ← kontaktTargetVersion gates available features ``` -- The main module is selected in Kontakt's *Instrument Options* dialog (KOMPLETE UI section), which also sets instrument width/height. -- `kontaktTargetVersion` in `Resources/info/library.json` determines the available Komplete UI feature set. Check `references/troubleshooting.md` for the version table if a feature seems missing. +- The main module is selected in Kontakt's *Instrument Options* dialog (KOMPLETE UI section), + which also sets instrument width/height. +- Import paths resolve from `komplete_scripts/`, never relative to the importing file. + +## Version ceiling -### Kontakt Controls package (optional) +`kontaktTargetVersion` in `Resources/info/library.json` is the ceiling. Read it, map it to a +kscript language version via `references/kontakt-versions.md`, and treat that language +version as the limit when using anything from the language references. If a feature seems +missing, check both tables before assuming an error. -Beginner-friendly controls that bind to KSP controls via `control_id`. **Not built in** — the user must download it manually and copy its folder into `Resources/komplete_scripts/`. Before using it, verify the `kontakt_controls/` folder exists next to `main.kscript` (existence check only — do not read the folder's source to learn the API). If missing, tell the user to download it from the Native Instruments homepage and copy it in. Import: `import * from kontakt_controls`. +## Kontakt Controls package (optional) -**API source of truth is `references/kontakt-controls.md`, not the installed folder.** Read the md for every control's API. Only fall back to reading the actual `kontakt_controls/` source when Kontakt reports an API error (unknown symbol, wrong parameter, missing modifier) on code that matches the md — that signals the installed package version has drifted from the reference. When that happens, note the mismatch to the user. +Beginner-friendly controls that bind to KSP controls via `control_id`. **Not built in** — the +user must download it manually and copy its folder into `Resources/komplete_scripts/`. Before +using it, verify the `kontakt_controls/` folder exists next to `main.kscript` (existence check +only — do not read the folder's source to learn the API). If missing, tell the user to +download https://storage.googleapis.com/ni-developer-platform/kontakt_controls.zip and copy +the folder in. Import: `import * from kontakt_controls`. -### KSP connection basics +**API source of truth is `references/kontakt-controls.md`, not the installed folder.** Read +the md for every control's API. Only fall back to reading the actual `kontakt_controls/` +source when Kontakt reports an API error (unknown symbol, wrong parameter, missing modifier) +on code that matches the md — that signals the installed package version has drifted from the +reference. When that happens, note the mismatch to the user. -Instrument logic lives in KSP (Kontakt's script editor); Komplete UI is the view layer. KSP declares UI controls and calls `expose_controls` in `on init`; kscript binds to them by control id (KSP name without the `$`): +## KSP connection basics + +Instrument logic lives in KSP (Kontakt's script editor); Komplete UI is the view layer. KSP +declares UI controls and calls `expose_controls` in `on init`; kscript binds to them by +control id (KSP name without the `$`): ``` on init @@ -57,46 +91,66 @@ on init end on ``` -Details and per-control classes: `references/kontakt-package.md`. +KSP connections are fixed at load time, so **declare them globally**, never inside a +component. Details and per-control classes: `references/kontakt-package.md`; the full binding +pattern: `references/kontakt-integration.md`. ## Development loop (Kontakt MCP server) -Kontakt exposes an MCP server that closes the feedback loop: **saving a .kscript file makes Kontakt reload the instrument automatically** — no manual reload step. Log output (compile errors, warnings, `print(...)` output) is fetched via MCP. Depending on whether you're editing an instrument or a tool, use `instrument_get_kscript_messages` or `tool_get_kscript_messages`. +Kontakt exposes an MCP server that closes the feedback loop: **saving a .kscript file makes +Kontakt reload the instrument automatically** — no manual reload step. Log output (compile +errors, warnings, `print(...)` output) is fetched via MCP. Depending on whether you're editing +an instrument or a tool, use `instrument_get_kscript_messages` or `tool_get_kscript_messages`. + +Use the `kontakt` MCP server's tools whenever it is available — the server is the source of +truth for what it offers, so read its tool list and descriptions rather than assuming a fixed +set. Load the schemas via ToolSearch first if they are deferred. Look up the id of the +instrument or tool you are editing once at session start; usually exactly one is loaded +during development — if several, ask the user which one. -Tools (load schemas via ToolSearch first if deferred): -- `list_instruments` — instruments loaded in the rack. Call once at session start to get the instrument id. Usually exactly one instrument during development; if several, ask the user which one. -- `list_tools` — tools loaded in the rack -- `instrument_get_kscript_messages` — errors, warnings, and `print` output for an instrument. Call after every edit. -- `tool_get_kscript_messages` — errors, warnings, and `print` output for an tool. Call after every edit. +If the server is not registered, run `/setup-mcp` (see the plugin README). ### The loop — run until clean After **every** edit to a `.kscript` file: 1. Save the file (the Write/Edit tool does this). Kontakt reloads automatically. -2. Call `get_instrument_kscript_messages` for the instrument or `tool_get_kscript_messages` for the tool. -3. Errors present → match against `references/troubleshooting.md` (Common Errors), fix, repeat from 1. Do not ask the user between iterations. +2. Call `instrument_get_kscript_messages` for an instrument, or + `tool_get_kscript_messages` for a tool. +3. Errors present → match against `komplete-script:language` → `references/troubleshooting.md` + (Common Errors), fix, repeat from 1. Do not ask the user between iterations. 4. Warnings → fix if clearly yours; report otherwise. -5. Clean → done. Report result to the user. +5. Clean → done. Report the result to the user. -If messages look stale or empty when an error is expected, re-fetch once; if the MCP server is unreachable, tell the user to check that Kontakt is running with Developer Mode enabled (Options → Developer tab). +If messages look stale or empty when an error is expected, re-fetch once; if the MCP server is +unreachable, tell the user to check that Kontakt is running with Developer Mode enabled +(Options → Developer tab). -Use `print("...")` in kscript for temporary debug logging — output arrives via `get_instrument_kscript_messages`. Remove debug prints before finishing. +Use `print("...")` in kscript for temporary debug logging — output arrives through the same +MCP call. Remove debug prints before finishing. ## Working with non-programmer users The target user often cannot code. Therefore: -- Translate their intent ("I want a big filter knob on the left") into kscript yourself; never ask them to write or read code. -- Explain results in UI terms ("the knob now sits in the top-left and controls the filter"), not code terms. -- When their request is ambiguous (placement, size, color, behavior), ask a short concrete question with options rather than guessing. -- Anything that must happen inside Kontakt's own UI (creating the resource container, setting the main module, instrument size, editing KSP in the Script Editor, enabling Developer Mode) you cannot do for them — give exact click-path instructions (see `references/troubleshooting.md` and the setup notes above). +- Translate their intent ("I want a big filter knob on the left") into kscript yourself; never + ask them to write or read code. +- Explain results in UI terms ("the knob now sits in the top-left and controls the filter"), + not code terms. +- When their request is ambiguous (placement, size, color, behavior), ask a short concrete + question with options rather than guessing. +- Anything that must happen inside Kontakt's own UI (creating the resource container, setting + the main module, instrument size, editing KSP in the Script Editor, enabling Developer Mode) + you cannot do for them — give exact click-path instructions. - Verify every change through the MCP loop before telling them it works. ## Hard rules -1. **Never invent API.** Every component, modifier, function, parameter label, and enum case you write must appear in a reference file. If it's not there, say so and check `references/troubleshooting.md` / ask the user, rather than guessing. +1. **Load `komplete-script:language` before writing kscript.** Never invent API — + every component, modifier, function, parameter label and enum case must appear in a + reference file. 2. All `.kscript` files must live under `Resources/komplete_scripts/`. 3. The main module must `export var main = `. -4. Run the MCP feedback loop after every edit; never declare success on unverified code. -5. Respect `kontaktTargetVersion` — don't use features newer than the target version. +4. KSP connections are declared globally, not inside components. +5. Run the MCP feedback loop after every edit; never declare success on unverified code. +6. Respect `kontaktTargetVersion` — don't use features newer than the mapped language version. diff --git a/plugins/kontakt-developer/skills/komplete-script/references/kontakt-integration.md b/plugins/kontakt-developer/skills/komplete-script/references/kontakt-integration.md new file mode 100644 index 0000000..44b41e6 --- /dev/null +++ b/plugins/kontakt-developer/skills/komplete-script/references/kontakt-integration.md @@ -0,0 +1,52 @@ + + +# Kontakt Integration Pattern + +Language syntax is in `komplete-script:language` → `references/language.md`. This +file covers only how a kscript UI connects to the Kontakt Engine through KSP. + +Connect to KSP controls via the `kontakt` package. KSP connections are fixed at load time, so declare them **globally**, not inside a component: + +```kscript +import { VStack, Text, Arc, DragGesture, Padding } from ui +import { KSPKnob } from kontakt + +var reverb_knob = KSPKnob(id: "reverb") // connects to KSP ui_knob "reverb" + +component ReverbSend { + accumulated_delta: Float = 0.0 // carries sub-integer drag remainder + + VStack(spacing: 4) { + Arc( + color: Color(0xFF000000), + start_angle: Angle(degrees: 135), + angle: Angle(degrees: reverb_knob.normalized_value * 270), + ) with { + DragGesture(fun (event) { + var dy = event.delta.y / event.frame.height + self.accumulated_delta = self.accumulated_delta - dy * (reverb_knob.max - reverb_knob.min) + var steps = self.accumulated_delta.to_int() + if steps != 0 { + reverb_knob.value = (reverb_knob.value + steps).clamped(min: reverb_knob.min, max: reverb_knob.max) + self.accumulated_delta = self.accumulated_delta - steps + } + }) + } + Text(reverb_knob.label) // reactive: updates with KSP label + Text("\{reverb_knob.value}") // reactive: updates with KSP value + } with { + Padding(16) + } +} + +export var main: Component = ReverbSend() +``` + +`KSPKnob` exposes reactive `value` (Int), `min`, `max`, `normalized_value` (Float 0.0–1.0), and `label`. The `kontakt controls` package provides ready-made `Knob`/`Slider` components wrapping this pattern. Colors are constructed as `Color(0xAARRGGBB)` (e.g. `Color(0xFFFF0000)` opaque red). + +## Rules + +- **KSP connections must be declared globally.** They are fixed at load time and do not + belong inside a component. +- Per-control classes and their full property sets: `references/kontakt-package.md`. +- Ready-made KSP-connected controls: `references/kontakt-controls.md`. diff --git a/plugins/kontakt-developer/skills/komplete-script/references/kontakt-versions.md b/plugins/kontakt-developer/skills/komplete-script/references/kontakt-versions.md new file mode 100644 index 0000000..05f9810 --- /dev/null +++ b/plugins/kontakt-developer/skills/komplete-script/references/kontakt-versions.md @@ -0,0 +1,51 @@ + + +# Kontakt versions & the kscript language version + +Kontakt embeds a specific Komplete Script (kscript) language version. Developers working in +Kontakt know the Kontakt version; the language reference in +`komplete-script:language` → `references/versions.md` is keyed by **language** +version. Use the mapping below to translate between the two. + +## Kontakt release → kscript version + +| Kontakt | kscript | +|---------|---------| +| 8.0.0 | 1.0 | +| 8.4.0 | 1.1 | +| 8.5.0 | 1.2 | +| 8.5.1 | 1.3 | +| 8.9.0 | 1.5 | +| 8.11.0 | 1.8 | +| 8.12.0 | 1.9 | + +**A Kontakt release not listed inherits the kscript version of the nearest listed release +below it.** So 8.1, 8.2, 8.2.1 and 8.3 → 1.0; 8.6, 8.7 and 8.8 → 1.3; 8.10 → 1.5. kscript +1.4, 1.6 and 1.7 were never shipped in a public Kontakt release. + +## Determining the ceiling + +`kontaktTargetVersion` in `Resources/info/library.json` declares the oldest Kontakt version +the instrument must run on. It gates the whole feature set: + +1. Read `kontaktTargetVersion`. +2. Map it to a kscript version with the table above. +3. Treat that kscript version as the ceiling for everything in the language references, and + the Kontakt version itself as the ceiling for the Kontakt-only features below. + +Never use a feature newer than the target. + +## Kontakt-specific feature availability + +Language, stdlib and `ui` changes are **not** listed here — see +`komplete-script:language` → `references/versions.md`. + +| Kontakt | Kontakt-specific additions / changes | +|---|---| +| 8.0 (beta) | Komplete UI public beta; `komplete_scripts` folder in the resource container; UI loaded via `load_komplete_ui("module_name")` from KSP | +| 8.1 (beta) | `KSPTextEdit`, `KSPLabel` | +| 8.2 | Kontakt Controls `XYPad` and `Stepper`; `TogglePolicy` → `TriggerPolicy`. **Breaking:** `load_komplete_ui()` removed — the main module is selected in *Instrument Options* instead | +| 8.3 | `KSPTable.length` | +| 8.4 | `text` property on `KSPButton`/`KSPKnob`/`KSPSwitch`/`KSPValueEdit`; `KSPTextEdit` `text` setter | +| 8.8 | `KSPMenu.entries`; `Help` modifier (Info Pane help text, `info_hints.json`) | +| 8.12 | `kontakt` module gains `Instrument`/`instrument`, `Group`, `GroupList`, `GroupListIterator`, `Zone`, `ZoneList`, `ZoneListIterator`, `Sample`, `load_sample`, `library_path` | diff --git a/plugins/kontakt-developer/skills/komplete-script/references/ksp-migration.md b/plugins/kontakt-developer/skills/komplete-script/references/ksp-migration.md new file mode 100644 index 0000000..2637966 --- /dev/null +++ b/plugins/kontakt-developer/skills/komplete-script/references/ksp-migration.md @@ -0,0 +1,188 @@ + + +# KSP → Komplete UI Migration + +Side-by-side KSP and kscript for developers coming from the Kontakt Script Processor. +Language details are in `komplete-script:language` → `references/language.md`. +Komplete Script mixes imperative code (logic: variables, functions, loops) and declarative code (UI: components, modifiers, layout). Unlike KSP's global step-by-step style, the UI is described as what it should look like given state (reactivity) — no manual update logic. + +## Variables + +Declared anywhere in imperative scope, must be initialized (no defaults), type usually inferred: + +```txt +declare $test := -1 +``` +```kscript +var test_implicit = -1 +var test_explicit: Int = -1 // optional explicit type +``` + +## Arrays and Maps + +No type label, no fixed size, dynamic growth, multidimensional supported: + +```txt +declare %presets[10 * 3] := ( ... + { 1 } 8, 8, 8, 0, 0, 0, 0, 0, ... +``` +```kscript +var presets = [ + [8, 8, 8, 0, 0, 0, 0, 0], + [8, 8, 8, 8, 0, 0, 0, 0], +] +var empty: [[Int]] = [[]] // explicit type required when initializing empty +empty.append([0, 0, 5, 3, 2, 0, 0, 0]) +``` + +Maps (no KSP equivalent) — key-value lookup returns an optional: + +```kscript +var preset_data = ["Warm": ["cutoff": 80, "resonance": 20]] +var warm = preset_data["Warm"] // optional — nil if no match +if warm != nil { + var cutoff = warm!["cutoff"] +} +``` + +## Functions + +```txt +function do_something() + { body } +end function +call do_something +``` +```kscript +fun do_something() { /* body */ } +do_something() + +// arguments + multiple return values: +fun split_name(full_name: String) -> (String, String) { + var parts = full_name.split(separator: " ") + return parts[0], parts[1] +} +var first, last = split_name(full_name: "Maria Philipps") +``` + +`return` exits the function like `exit` in KSP, but can also carry values. + +## Entry point + +No `on init`. The main module (linked in Instrument Options) runs once at instrument load — its top-level scope is the `on init` equivalent. The main module MUST export a UI component: + +```kscript +import { Text } from ui +export var main = Text("Hello World") // required in the main module +``` + +## Project structure + +Any number of modules (files); no script-slot limit. All code goes in the instrument's Resource Container under the `komplete_scripts` folder; import modules from there. + +## UI elements + +Import the UI package: `import * from ui`. Components are more basic than KSP widgets (Rectangle, Text, …) and are composed. KSP UI controls remain the data bridge to the Kontakt Engine — the `kontakt` package connects Komplete UI components to KSP controls. The optional `kontakt_controls` package provides ready-made KSP-connected components (Slider, Knob, …): + +```kscript +import * from ui +import * from kontakt_controls + +export component Main { + ZStack { + Rectangle(color: Color(0xFF2A2A2A)) // background + Slider(control_id: "my_slider", label: "My Slider") // control_id = KSP ui control id + } +} +export var main = Main() +``` +```txt +on init + declare ui_slider $my_slider (0, 100) + set_control_par(get_ui_id($my_slider), $CONTROL_PAR_DEFAULT_VALUE, 0) + expose_controls +end on +``` + +Declarative for loop replaces repetitive declarations: + +```kscript +HStack { + for text in ["Pan", "Vibrato", "Tune"] { + Slider(control_id: text, label: text) + } +} +``` + +## Callbacks and reactivity + +KSP callbacks are global (`on ui_control`, `on ui_update`); Komplete UI callbacks are attached to specific components/modifiers (predefined ones like Canvas or DragGesture, or your own function properties): + +```txt +on init + declare ui_text_edit @label_name + set_control_par_str(get_ui_id(@label_name), $CONTROL_PAR_TEXT, "Edit me") +end on +on ui_control (@label_name) + message(@label_name & " it is!") +end on +``` +```kscript +import { TextInput } from ui + +export component Main { + text: String = "Edit me" + TextInput(self.$text, on_submitted: fun () { + print("\{self.text} it is!") + }) +} +export var main = Main() +``` + +Reactivity: describe the UI as a function of state; no manual show/hide (`$CONTROL_PAR_HIDE`) logic: + +```kscript +import * from ui + +component Button { + @binding syncing: Bool + Text("Sync") with { + Padding(5) + Background { + Rectangle(color: self.syncing ? Color(0x30000000) : Color(0x70000000), radius: 2) + } + TapGesture(fun (event) { self.syncing = not self.syncing }) + } +} + +export component Main { + syncing: Bool = false + VStack { + Button(syncing: self.$syncing) + if self.syncing { + Text("Syncing...") // shown/hidden automatically with state + } + } +} +export var main = Main() +``` + +## Layout + +Container components replace pixel placement: `VStack` (vertical), `HStack` (horizontal), `ZStack` (overlapping). Absolute positioning is still possible with the `Position` modifier inside a ZStack (analog of `ui_panel` + `CONTROL_PAR_POS_X/Y`): + +```kscript +component Bar { + ZStack { + Knob(control_id: "attack", label: "Attack") with { Position(x: 0, y: 0) } + Knob(control_id: "decay", label: "Decay") with { Position(x: 50, y: 0) } + } + with { + Frame(width: 200, height: 70) // ZStack size + Position(x: 100, y: 20) // ZStack position in instrument UI + } +} +``` + +Note: instrument UI size is set in Instrument Options → Instrument; no `make_perfview` needed. + diff --git a/plugins/kontakt-developer/skills/komplete-script/references/troubleshooting.md b/plugins/kontakt-developer/skills/komplete-script/references/troubleshooting.md deleted file mode 100644 index 5ee703a..0000000 --- a/plugins/kontakt-developer/skills/komplete-script/references/troubleshooting.md +++ /dev/null @@ -1,266 +0,0 @@ - - -# Troubleshooting & Migration Reference - -## Common Errors - -### Error: `unwrapping a nil value` - -Cause: `!` force-unwrap on an optional that was `nil` at runtime (e.g. map lookup for a missing key returns nil). - -```kscript -var values = ["a": 0, "b": 1, "c": 2] -var fails = values["missing_key"]! // error — map lookup returns nil -``` - -Fix: check for `nil` before unwrapping, or use a ternary fallback: - -```kscript -var result = values["missing_key"] != nil ? values["missing_key"]! : -1 - -if values["missing_key"] != nil { - print("Found: \{values["missing_key"]!}") -} -``` - -### Components recreated / flickering / state resets (creating components inside a function) - -Symptom: components are recreated repeatedly, causing flickering or unexpected state resets. No error message. - -Cause: calling a function inside a reactive expression (property, computed state, component body) tracks the function's *arguments* as dependencies. When arguments change, the function reruns — if it creates components, new instances replace old ones. - -```kscript -fun make_item(text: String) -> (Component) { - return Text(text) // new Text created every time text changes -} -``` - -Fix: use a `template` instead — templates forward parameters lazily and create no reactive dependency on their arguments: - -```kscript -var make_item = template (text: String) { - Text(text) // stable — not recreated when text changes -} -``` - -## Known Issues - -- **Differently composed UTF-8 characters may not compare equal.** Pre-composed vs decomposed forms look identical but can compare unequal in string comparison. (Several string methods were fixed in 8.2; comparison itself remains a known issue.) -- **Spacer size incorrect if modifiers are applied to it.** A modifier applied to a `Spacer` in a stack can alter its size unexpectedly. Workaround: avoid applying modifiers directly on `Spacer`. -- **Indexing the iterated container inside a declarative for loop** (fixed in Kontakt 8.4): accessing `presets[i]` inside `for i, preset in presets.enumerated() { ... }` can cause out-of-bounds errors when elements are removed. Workaround (pre-8.4): use only the iteration values (`preset`), not `presets[i]`. -- **Same state in a declarative for loop's sequence and its body → "cycle detected" error** (fixed in Kontakt 8.4): e.g. using `self.offset` both in `presets.subsequence(from: self.offset, length: 1)` and in the loop body. Workaround (pre-8.4): embed the total index/id into the data itself and avoid using the state inside the body: - -```kscript -class Preset { - id: Int - name: String -} -// iterate: for preset in presets.subsequence(from: self.offset, length: 1) { Text("\{preset.id}: \{preset.name}") } -``` - -## FAQ - -- **Komplete UI vs Komplete Script?** Komplete UI = the UI framework (the `ui` package). Komplete Script = the underlying programming language used in `.kscript` files (usable for general scripting too). -- **Reporting issues?** Join the NI Developer Slack (invite via builder-experience-team@native-instruments.com). - -## KSP → Komplete UI Migration - -Komplete Script mixes imperative code (logic: variables, functions, loops) and declarative code (UI: components, modifiers, layout). Unlike KSP's global step-by-step style, the UI is described as what it should look like given state (reactivity) — no manual update logic. - -### Variables - -Declared anywhere in imperative scope, must be initialized (no defaults), type usually inferred: - -```txt -declare $test := -1 -``` -```kscript -var test_implicit = -1 -var test_explicit: Int = -1 // optional explicit type -``` - -### Arrays and Maps - -No type label, no fixed size, dynamic growth, multidimensional supported: - -```txt -declare %presets[10 * 3] := ( ... - { 1 } 8, 8, 8, 0, 0, 0, 0, 0, ... -``` -```kscript -var presets = [ - [8, 8, 8, 0, 0, 0, 0, 0], - [8, 8, 8, 8, 0, 0, 0, 0], -] -var empty: [[Int]] = [[]] // explicit type required when initializing empty -empty.append([0, 0, 5, 3, 2, 0, 0, 0]) -``` - -Maps (no KSP equivalent) — key-value lookup returns an optional: - -```kscript -var preset_data = ["Warm": ["cutoff": 80, "resonance": 20]] -var warm = preset_data["Warm"] // optional — nil if no match -if warm != nil { - var cutoff = warm!["cutoff"] -} -``` - -### Functions - -```txt -function do_something() - { body } -end function -call do_something -``` -```kscript -fun do_something() { /* body */ } -do_something() - -// arguments + multiple return values: -fun split_name(full_name: String) -> (String, String) { - var parts = full_name.split(separator: " ") - return parts[0], parts[1] -} -var first, last = split_name(full_name: "Maria Philipps") -``` - -`return` exits the function like `exit` in KSP, but can also carry values. - -### Entry point - -No `on init`. The main module (linked in Instrument Options) runs once at instrument load — its top-level scope is the `on init` equivalent. The main module MUST export a UI component: - -```kscript -import { Text } from ui -export var main = Text("Hello World") // required in the main module -``` - -### Project structure - -Any number of modules (files); no script-slot limit. All code goes in the instrument's Resource Container under the `komplete_scripts` folder; import modules from there. - -### UI elements - -Import the UI package: `import * from ui`. Components are more basic than KSP widgets (Rectangle, Text, …) and are composed. KSP UI controls remain the data bridge to the Kontakt Engine — the `kontakt` package connects Komplete UI components to KSP controls. The optional `kontakt_controls` package provides ready-made KSP-connected components (Slider, Knob, …): - -```kscript -import * from ui -import * from kontakt_controls - -export component Main { - ZStack { - Rectangle(color: Color(0xFF2A2A2A)) // background - Slider(control_id: "my_slider", label: "My Slider") // control_id = KSP ui control id - } -} -export var main = Main() -``` -```txt -on init - declare ui_slider $my_slider (0, 100) - set_control_par(get_ui_id($my_slider), $CONTROL_PAR_DEFAULT_VALUE, 0) - expose_controls -end on -``` - -Declarative for loop replaces repetitive declarations: - -```kscript -HStack { - for text in ["Pan", "Vibrato", "Tune"] { - Slider(control_id: text, label: text) - } -} -``` - -### Callbacks and reactivity - -KSP callbacks are global (`on ui_control`, `on ui_update`); Komplete UI callbacks are attached to specific components/modifiers (predefined ones like Canvas or DragGesture, or your own function properties): - -```txt -on init - declare ui_text_edit @label_name - set_control_par_str(get_ui_id(@label_name), $CONTROL_PAR_TEXT, "Edit me") -end on -on ui_control (@label_name) - message(@label_name & " it is!") -end on -``` -```kscript -import { TextInput } from ui - -export component Main { - text: String = "Edit me" - TextInput(self.$text, on_submitted: fun () { - print("\{self.text} it is!") - }) -} -export var main = Main() -``` - -Reactivity: describe the UI as a function of state; no manual show/hide (`$CONTROL_PAR_HIDE`) logic: - -```kscript -import * from ui - -component Button { - @binding syncing: Bool - Text("Sync") with { - Padding(5) - Background { - Rectangle(color: self.syncing ? Color(0x30000000) : Color(0x70000000), radius: 2) - } - TapGesture(fun (event) { self.syncing = not self.syncing }) - } -} - -export component Main { - syncing: Bool = false - VStack { - Button(syncing: self.$syncing) - if self.syncing { - Text("Syncing...") // shown/hidden automatically with state - } - } -} -export var main = Main() -``` - -### Layout - -Container components replace pixel placement: `VStack` (vertical), `HStack` (horizontal), `ZStack` (overlapping). Absolute positioning is still possible with the `Position` modifier inside a ZStack (analog of `ui_panel` + `CONTROL_PAR_POS_X/Y`): - -```kscript -component Bar { - ZStack { - Knob(control_id: "attack", label: "Attack") with { Position(x: 0, y: 0) } - Knob(control_id: "decay", label: "Decay") with { Position(x: 50, y: 0) } - } - with { - Frame(width: 200, height: 70) // ZStack size - Position(x: 100, y: 20) // ZStack position in instrument UI - } -} -``` - -Note: instrument UI size is set in Instrument Options → Instrument; no `make_perfview` needed. - -## Feature availability by Kontakt version - -| Version | Notable additions / changes | -|---|---| -| 8.0 (beta) | Komplete UI public beta; `komplete_scripts` folder in resource container; loaded via `load_komplete_ui("module_name")` from KSP | -| 8.1 (beta) | `KSPTextEdit`, `KSPLabel` | -| 8.2 | `Map`, `Angle` (replaces Float angles in Rotation/Arc/Canvas), `Range`, `enumerated()`, `clamped`, type deduction for templates/function expressions, unicode code points in strings, named args in any order; text modifiers (LineLimit, FontFamily, FontSize, MultilineTextAlignment, TextColor); XYPad, Stepper. **Breaking:** `load_komplete_ui()` removed (select module in Instrument Options instead); `Array.filter` → `filtered(by:)`; `subsequence(count:)` → `length:`; `String.replace_all` → `replacing_all`; `String.removing` removed; if-else expression → ternary `a ? b : c`; Ring → Arc; font size is Int; `FontFamily` (class) → `FontFamilyName`; Math `clamp`/`clampf`, `degrees_to_radians`/`radians_to_degrees` removed; `TogglePolicy` → `TriggerPolicy`; exported types must not refer to un-exported types (annotate e.g. `export var main: Component = Main()`); TapGesture cancel/up semantics changed | -| 8.2.1 | Popover fixes (initial visible, anchor following, reload crash) | -| 8.3 | `KSPTable.length` | -| 8.4 | `TextInput` component; `text` property on KSPButton/KSPKnob/KSPSwitch/KSPValueEdit; KSPTextEdit `text` setter; fixes for declarative for/if update-before-body and cycle errors | -| 8.5 | Stable `Drag`/`Drop` modifiers; enums as map keys; compile-time improvements | -| 8.5.1 | Popover `visible: Bool` constructor | -| 8.8 | `KSPMenu.entries`; `Help` modifier (Info Pane help text) | -| 8.9 | Default function arguments (`fun f(p: Int = 0)`); `\r` escape in strings | -| 8.10 | `atan2` | -| 8.11 | `letter_spacing` (Text, TextInput) + `LetterSpacing` modifier; `line_height` (Text) + `LineHeight` modifier; faster number parsing; subscript operator (`at`/`assign`) for custom classes | -| 8.12 | Method overloading; `audio_components`, `path`, and `uri` packages; Kontakt module `Instrument`/`instrument`/`Group`/`GroupList`/`GroupListIterator`/`load_sample`/`library_path`/`Sample`/`Zone`/`ZoneList`/`ZoneListIterator`; `Array.append` overloaded to accept `[Element]`, replacing `append_all` |