Project Structure¶
A simple IVLS instrument might have a handful of nodes in a single file. A production instrument like Tokyo Scoring Strings or Polycore has dozens of nodes, multiple engine boxes, specialized subsystems, and thousands of lines of configuration. How you organize all of this matters -- for development speed, for maintainability, and for collaboration.
This chapter walks through the structural patterns that have evolved across IVLS products, from the earliest monolithic layouts to the modern separated architecture.
The Two Entry Points¶
Every IVLS product has two critical files:
build.ksp¶
The build file sits at the root of the Code directory. It contains compiler pragmas and import statements -- nothing else:
{ #pragma compile_with remove_whitespace }
{ #pragma compile_with optimize_code }
{ #pragma compile_without compact_variables }
{ #pragma compile_without add_compile_date }
{ #pragma compile_without combine_callbacks }
{ #pragma compile_without sanitize_exit_command }
import "nodes/"
import "imports/"
{ #pragma save_compiled_source ../Resources/scripts/compiled.txt }Pragmas control the SKSP compiler's behavior -- whitespace removal, code optimization, variable compaction. The import statements pull in the node directories. The save_compiled_source pragma writes the final compiled output.
ivls-product.ksp¶
The product file defines the node assembly and system configuration:
define ivls.THREADS := 1
define IVLS_NODES += Phr.Startup, ...
Phr.SoundData, ...
Phr.Boxes.Bundle, ...
Phr.Playback.Bundle, ...
Phr.GUI.Bundle, ...
Phr.Browser.Bundle, ...
Phr.Purge, ...
Phr.NKS, ...
Console
import "_IVLS/builder.ksp"This file lists every node in the product's assembly via IVLS_NODES. The final import "_IVLS/builder.ksp" pulls in the IVLS engine, which reads the assembly and compiles the product.
The base-features / impl-features Separation¶
Production instruments separate their nodes into two categories:
base-features/ -- nodes that define interfaces, data structures, and abstract behavior. These nodes declare what a system needs but don't fully implement product-specific details. They often contain __RUN_CB__ hooks that impl-features fill in.
impl-features/ -- nodes that provide the product-specific implementation. These respond to custom callbacks declared in base-features, override functions, and fill in product-specific logic.
For example, in Tokyo Scoring Strings:
base-features/tokyo-releases.kspdefines the release spawning system, the adaptive release data structures, and the volume analysis functionsimpl-features/tss-tact-dynamics.kspimplements the product-specific dynamics processing that the base system calls
This separation lets multiple products share base-features (Tokyo Scoring Strings and Tokyo Scoring Solo Strings share the same base) while each product provides its own impl-features.
Interface Contracts¶
Base-feature nodes often document their requirements as comments listing the functions or callbacks that impl-features must provide. For example, a base node might require:
{
REQUIRED FUNCTIONS:
- tky.get_instr_min_note() -> int
- tky.get_instr_max_note() -> int
- tky.get_artic_sync(artic) -> int
- tky.purge.set_articulation_state(artic, state)
}These are informal contracts -- the compiler will fail if a required function is missing, but the documentation makes the dependency explicit for developers.
Modern Folder Layout¶
The modern IVLS project layout organizes nodes by concern:
Code/
build.ksp
nodes/
ivls-product.ksp
base-features/
sound-data.ksp
features/
boxes/
globals-data.ksp
globals-meta.ksp
globals-ui-binds.ksp
layers-data.ksp
layers-meta.ksp
layers-ui.ksp
playback/
events.ksp
flows.ksp
gui/
ui-logic.ksp
purge.ksp
browser.ksp
nks.ksp
impl-features/
product-sound-data.kspKey directories:
boxes/-- engine box parameter systems, each split into the three-file pattern (data, meta, ui-binds)playback/-- flow definitions, event trigger nodes, voice processing nodesgui/-- UI logic nodes, display updates, page managementimpl-features/-- product-specific implementations of base interfaces
Node Bundle Organization¶
With many nodes, the IVLS_NODES list can become unwieldy. Node bundles group related nodes under a single define:
define Phr.Boxes.Bundle += Phr.Boxes.GlobalData, ...
Phr.Boxes.GlobalMeta, ...
Phr.Boxes.GlobalUIBinds, ...
Phr.Boxes.KeyData, ...
Phr.Boxes.KeyMeta, ...
Phr.Boxes.KeyUIBindsThe += operator appends to the define. Each feature file adds its own nodes to the appropriate bundle:
{ In events.ksp }
define Phr.Playback.Bundle += Phr.PlayEvent, ...
Phr.Modify.SetZonePars
{ In flows.ksp }
define Phr.Playback.Bundle += Phr.PlaybackLogicThe product file then lists bundles instead of individual nodes:
define IVLS_NODES += Phr.Startup, ...
Phr.Boxes.Bundle, ...
Phr.Playback.Bundle, ...
Phr.GUI.BundleThis keeps the assembly list short and makes it easy to add or remove entire feature groups.
Evolution of Project Structure¶
IVLS project structure has evolved through several generations:
Early Products (Monolithic)¶
Early products placed all nodes in a small number of large files. Engine boxes, flows, playback logic, and UI were mixed together. This worked for simple instruments but became difficult to navigate as complexity grew.
Middle Generation (Feature Separation)¶
Products began separating by feature: one file for flows, one for events, one for the browser, one for each engine box. The base-features / impl-features split emerged to support shared codebases between related products.
Modern Products (Three-File Boxes, Organized Directories)¶
Instruments like Sylvan Phrases and Polycore use the full modern layout: engine boxes consistently follow the three-file pattern (data, meta, ui-binds), features are organized into subdirectories by concern (boxes, playback, gui), and node bundles keep the assembly manageable.
The key insight driving this evolution: a file should contain nodes that share a single concern, and directories should group concerns into subsystems. An engine box's data, metadata, and UI bindings are three aspects of the same concern -- they belong in the same directory. Playback logic and GUI logic are different concerns -- they belong in different directories.
There is no enforced project structure in IVLS. The compiler only cares about build.ksp, ivls-product.ksp, and the node assembly. But the patterns described here -- entry point separation, base/impl split, three-file boxes, bundle organization, concern-based directories -- have proven effective across a range of product complexities.