Skip to content

Clusters

Clusters combine pooled memory allocation (via the type system) with field validation and asynchronous dispatch. They provide structured data with lifecycle hooks and callback-driven processing. The Voice cluster is the canonical implementation.

Defining a Cluster

Member Definitions

Fields are defined via a MEMBERS define. For extensible clusters, members are split for library extension:

define Voice.MEMBERS := Voice.BASE_MEMBERS, Voice.ADD_MEMBERS

Libraries extend members using +=:

define Voice.ADD_MEMBERS += Stl.Modulation.MEMBERS
define Voice.ADD_MEMBERS += Stl.LegatoInfo.MEMBERS

Initialization Lists

Each cluster defines an INIT_LIST with default values matching the member order:

define Voice.INIT_LIST := Voice.BASE_INIT, Voice.ADD_INIT

declare Voice.BASE_INIT[] := (...
    -1, ... {input}
    -1, ... {runtime}
  TRUE, ... {auto_release}
    -1, ... {vo_parent}
    -1, ... {note}
     0, ... {volume}
     0, ... {pan}
     0  ... {tune}
)

The system merges all initialization lists at startup:

literate_macro(Voice.WriteInitList) on Voice.INIT_LIST

Validation Macros

Each field that requires validation gets a parameterized define:

define Voice.validate.input(#v#) := (in_range(#v#, 0, ivls.input_type.SIZE - 1))
define Voice.validate.note(#v#) := (in_range(#v#, 0, 127))
define Voice.validate.runtime(#v#) := (#v# >= -1)
define Voice.validate.event(#v#) := (BOOL.TRUE)

The dispatch system auto-validates before executing callbacks:

macro cluster.Validate(#cluster#, #member#)
    if not (#cluster#.validate.#member#(#cluster#[self].#member#))
        message('ArgFailure (#cluster#): #cluster#[self].#member# invalid: ' & #cluster#[self].#member#)
        self_invalid := TRUE
    end if
end macro

CLUSTER_TABLE Registration

Register clusters with the dispatch system using +=:

define CLUSTER_TABLE += Voice

This generates: task enumeration, memory pool/type infrastructure via type.Create, dispatch queue (adt.CreateQueue), and lifecycle hooks.

Dispatch System

Architecture

LCB Timer Tick -> cluster.tick() -> internal note events -> KRCB Handler -> ClusterName._process()

The _process taskfunc validates fields and executes the cluster callback:

taskfunc #cluster#._process(ref)
    declare self := ref
    declare self_invalid := FALSE
    declare self_auto_delete := TRUE

    if check_ref(#cluster#, self)
        literate_post_macro(cluster.Validate(#cluster#, #l#)) on #cluster#.MEMBERS

        if self_invalid = TRUE
            #cluster#.CB.Cancelled()
            message("Cluster [#cluster#] fired with invalid arguments! Dumping state:")
            cluster.DumpSelf(Voice)
        else
            #cluster#.CB()
        end if

        if self_auto_delete = TRUE
            #cluster#.DeleteInCB()
            #cluster#.delete(self)
        end if
    end if
end taskfunc

Control Variables

Variable Purpose
self Reference to the current cluster instance
self_invalid Set to TRUE to abort processing
self_auto_delete Set to FALSE to prevent automatic deletion

Voice Cluster Fields

Core / Hierarchy

Field Default Description
input -1 Input type (VOICE_ON, VOICE_OFF)
runtime -1 Runtime reference
auto_release TRUE Auto-release on parent release
vo_parent / vo_child -1 Parent/first child voice
vo_left / vo_right -1 Sibling references

Flow / MIDI / Sound

Field Default Description
flow / stage 0 / -1 Current flow and stage
note / vel / midi_ch -1 MIDI data
event -1 Kontakt event ID
volume / pan / tune 0 Sound parameters
dyn_layer / rr 1 / 0 Dynamic layer, round robin index

Lifecycle Hooks

macro Voice.Constructor(#ref#)
    declare ref := #ref#
    __RUN_CB__(VoiceConstructor)
end macro

macro Voice.Destructor(#ref#)
    declare ref := #ref#
    __RUN_CB__(VoiceDestructor)
end macro

Debugging

cluster.Dump(Voice, self)     // Dump all fields for a reference
cluster.DumpSelf(Voice)       // Shorthand within a callback

if check_ref(Voice, vo_ref)   // Validate before access
    { ... safe to access ... }
end if

Complete Example

{ Define }
define MyCluster.MEMBERS := target, value, callback_id

macro MyCluster.def()
    declare MyCluster.INIT[] := (-1, 0, -1)
    define MyCluster.validate.target(#v#) := (#v# >= -1)
    define MyCluster.validate.value(#v#) := (BOOL.TRUE)
    define MyCluster.validate.callback_id(#v#) := (#v# >= 0)
end macro

macro MyCluster.CB()
    declare target := MyCluster[self].target
    declare value := MyCluster[self].value
    { Process the cluster... }
end macro

{ Register in builder.ksp }
define CLUSTER_TABLE += MyCluster

{ Use }
declare my_ref := MyCluster.new()
MyCluster[my_ref].target := some_target
MyCluster[my_ref].value := 100
MyCluster.push(my_ref)