How to Use Python Packages for Product Data¶
Use Python packages to generate product configuration, NKA data files, and templated KSP code, keeping your data model in Python and your instrument logic in KSP.
Prerequisites¶
- Python 3.13+
- Understanding of
run<</read<<syntax in KSP - Familiarity with IVLS nodes and the build process
Steps¶
1. Create a Python package with pyproject.toml¶
Set up a standard Python package structure. The src/ layout keeps the package importable and installable:
my-product/
pyproject.toml
src/
myproduct/
__init__.py
models.py
Code/
ivls-product.ksp
Resources/
data/Create pyproject.toml:
[project]
name = "myproduct"
version = "0.1.0"
description = "Data package for My Product"
requires-python = ">=3.13"
dependencies = [
"ivls-sksp (>=0.5.1)"
]
[tool.poetry]
packages = [
{ include = "myproduct", from = "src" }
]
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
The ivls-sksp dependency provides the Jinja templating engine (setup_env, render_repo) used to render KSP from Python data. If your package needs to save NKA files, also add py-ksp as a dependency.
2. Install in development mode¶
Install your package so that run<< blocks can import it:
This makes your package available system-wide while letting you edit the source files in place.
3. Write the data model in Python¶
Define your product's data structures in Python. Use dataclasses or Pydantic models to represent sounds, articulations, configuration, etc:
{ src/myproduct/__init__.py }
from pathlib import Path
from dataclasses import dataclass
from ivls_sksp.templating import setup_env, render_repo
@dataclass
class MyProduct:
name: str
sound_names: list[str]
sound_ids: list[int]
families: list[str]
def create(base_path: str, product_name: str) -> MyProduct:
{ Build your data model from JSON, CSV, databases, etc. }
product = MyProduct(
name=product_name,
sound_names=["Piano", "Strings", "Pad"],
sound_ids=[100, 200, 300],
families=["Keys", "Orchestral", "Synth"]
)
return product
def emit(base_path: str, product: MyProduct) -> str:
{ Save NKA files }
from py_ksp.kontakt import NKA
resource_path = str(Path(base_path).parent / 'Resources' / 'data')
NKA.save(f'{product.name}.sound_names', resource_path, product.sound_names)
NKA.save(f'{product.name}.sound_ids', resource_path, product.sound_ids)
{ Render KSP templates with product data as context }
env = setup_env(context={
'base_path': base_path,
'product_name': product.name,
'product': product,
})
locate_src = Path(__file__).parent / 'ivls'
return render_repo(env, str(locate_src))
The create() function builds the data model and optionally saves NKA files. The emit() function renders KSP templates using render_repo(), which walks a directory of .ksp template files and renders them with Jinja.
4. Write KSP templates¶
Place KSP template files in src/myproduct/ivls/. These files use Jinja syntax (<! !> for blocks, <: :> for expressions) with your Python objects available as context:
{ src/myproduct/ivls/ivls-product.ksp }
node MyProduct.SoundData:
cb Init:
define MyProduct.NUM_SOUNDS := <:len(product.sound_names):>
family my
declare !sound_names[MyProduct.NUM_SOUNDS]
declare sound_ids[MyProduct.NUM_SOUNDS]
end family
load_array(!my.sound_names, 2)
load_array(my.sound_ids, 2)
end node5. Use run<< in KSP to invoke your package¶
In your main ivls-product.ksp, use a run<< block to call create() and emit():
run<<
import myproduct
product = myproduct.create(base_path, 'my_product')
code = myproduct.emit(base_path, product)
>>
{ Emit the generated nodes }
<:code:>
{ Use product data in subsequent nodes }
node MyProduct.Setup:
cb Init:
define NUM_SOUNDS := <:len(product.sound_names):>
end node
The run<< block executes Python at compile time. All variables defined in it (like product and code) are available to subsequent Jinja expressions in the same file.
6. Generate NKAs from Python¶
For data that is too large for inline KSP (lookup tables, metadata arrays, etc.), save it as NKA files during create() and load them at runtime:
{ In your create() function }
from py_ksp.kontakt import NKA
NKA.save('my_product.trigger_data', resource_path, trigger_data_list)
NKA.save('my_product._meta', resource_path, meta_values_list)
Then load in KSP:
cb Init:
declare my_meta[NUM_ARTICS, META_PARAMS]
load_array(_my_meta, 2)NKA files support integers (% prefix), floats (? prefix), and strings (! prefix). The py_ksp.kontakt.NKA.save() function detects the type automatically from the first element.
The create() / emit() pattern¶
The standard pattern used across IVLS Python packages is:
create()-- builds data model, saves NKAs, returns a data objectemit()-- takes the data object, renders KSP templates, returns a code stringrun<<block in KSP -- calls both, injects the code with<:code:>
This separation means the same data object can be shared across multiple run<< blocks and used in Jinja expressions throughout the file. Both Modrix and Polycore follow this pattern.
Verify¶
- Run the IVLS build -- the
run<<block should execute without errors - Check that NKA files appear in
Resources/data/ - Confirm that the generated KSP compiles and the loaded arrays contain the expected data
Further reading¶
- Guide: run<< and read<< blocks for the compile-time Python execution model
- Guide: Templating for the Jinja template syntax used in KSP files
- Engine Box module reference for how generated metadata integrates with engine boxes