Skip to content

How to Build a TACT Product

Set up the TACT (The Articulation Control Thing) system for a multi-articulation instrument, from CSV configuration through to playback routing.

Prerequisites

  • An IVLS product with multiple articulation sample groups mapped in Kontakt
  • Python 3.13+ (for the CSV importer)
  • Understanding of flows and nodes

Steps

1. Create a TACT config CSV

Define your articulations in a CSV file named tact-config.csv. The importer reads specific labeled rows to build the articulation database.

,            Articulations
,            SUS_NORMAL,  STACCATO,  SPICCATO,  PIZZICATO,  TREMOLO,  LEGATO_BOW,  RELEASE

,            Names
,            "Sustain",   "Staccato", "Spiccato", "Pizzicato", "Tremolo", "Legato Bow", "Release"

,            Symbols
,            TRUE, FALSE

,            Values
,            1,    0

,            Meta
Group Index, 0,           1,          4,          8,          11,        12,           24
Group Count, 1,           1,          1,          1,          1,         2,            1
Vel Layers,  1,           3,          2,          3,          1,         1,            1
Num RR,      1,           4,          4,          3,          1,         6,            1
Offset Range,0,           0,          0,          0,          0,         0,            0
Legato State,FALSE,       FALSE,      FALSE,      FALSE,      FALSE,     TRUE,         FALSE
,            End

Key rows: Group Index maps to the Kontakt group, Group Count is how many consecutive groups the artic uses, Vel Layers and Num RR define the velocity/round-robin matrix. The Symbols and Values rows let you use symbolic names (like TRUE/FALSE) in the metadata rows.

2. Run the CSV importer

The importer reads the CSV, generates KSP artic ID constants and saves the metadata NKA:

cd Dev/
python tact-config-importer.py

The script walks the current directory for tact-config.csv files and produces two outputs:

  • A KSP file (e.g. tkystrings-tact-db.ksp) containing the tact.csv.ICB() macro with artic ID constants
  • An NKA metadata file (e.g. _tkystrings_meta.nka) with per-articulation parameter data

The generated KSP looks like:

{ DO NOT EDIT. RE-IMPORT USING PYTHON FOR CHANGES }
macro tact.csv.ICB()
    family tact
        const artic_id
            SUS_NORMAL
            STACCATO
            SPICCATO
            PIZZICATO
            TREMOLO
            LEGATO_BOW
            RELEASE
        end const

        declare !artic_names[TACT_CONFIG.NUM_ARTICS]
    end family
end macro

3. Create the TACT configuration file

Create a config file that imports the generated DB, sets dimensions, and defines the tact.config.ICB() and tact.config.Functions() macros:

import "my-product/my-tact-db.ksp"

define TACT_CONFIG.MAIN_SLOT := 0
define TACT_CONFIG.NUM_ARTICS := 7
define TACT_CONFIG.NUM_KS := 128
define TACT_CONFIG.USE_PAGINATOR := TRUE
define TACT_CONFIG.VISIBLE_ARTICS := 7
define TACT_CONFIG.TOTAL_TABLES := 1

macro tact.config.ICB()
    tact.csv.ICB()

    family tact
        { Keyswitch assignment array -- maps MIDI notes to artic IDs }
        declare ks_set[] := (0, 1, 2, 3, 4, 5, 6)
    end family

    { Load metadata NKA }
    declare my_meta[TACT_CONFIG.NUM_ARTICS, TACT.NUM_META_PARAMS]
    declare !my_artic_names[TACT_CONFIG.NUM_ARTICS]
    load_array(_my_meta, 2)
    load_array(!my_artic_names, 2)
end macro

macro tact.config.Functions()
    function tact.config.init_meta()
        util.array.copy(!my_artic_names, !tact.artic_names)
        util.array.copy(_my_meta, tact._meta)
    end function
end macro

4. Import TACT and assemble

In a TACT assembly file, set up any custom library hooks and import the TACT node system:

{ Define mic count and group offset for multi-mic instruments }
define MICS := 3
define MIC_OFFSET := 50

{ Custom TACT hooks -- required even if empty }
function tact.lib.update_volume(artic)
    { Apply volume to additional groups beyond the standard set }
end function

function tact.lib.custom_cb(ctrl, artic)
    { React to custom TACT parameter changes }
end function

import "_TACT3/node-import.ksp"

5. Add TACT.NodeBundle to IVLS_NODES

Register the TACT node bundle in your product assembly:

define IVLS_NODES += ..., ...
                     TACT.NodeBundle, ...
                     MyProduct.Features, ...
                     ...

6. Register TACT nodes in your evaluation flow

The critical flow pattern for TACT playback is: evaluate which articulation is active, apply fallbacks, then route to sound playback. Here is the standard pattern from Tokyo Scoring Strings:

node MyProduct.Flows:
    cb Flows:
        define FLOWS += my.flows.start, my.flows.play_sound

        { Articulation evaluation chain }
        ivls.register_node(my.flows.start, TACT.Modify.EvalArtics)
        ivls.register_node(my.flows.start, TACT.Modify.FallbackArtics)
        ivls.register_node(my.flows.start, Stl.Pedals)
        ivls.register_node(my.flows.start, MyProduct.Divert.PlaybackParse)

        { Sound playback chain }
        ivls.register_node(my.flows.play_sound, Stl.RoundRobins)
        ivls.register_node(my.flows.play_sound, TACT.Modify.TriggerArticVolume)
        ivls.register_node(my.flows.play_sound, TACT.Sys.Modify.Dynamics)
        ivls.register_node(my.flows.play_sound, TACT.Sys.SetADSR)
        ivls.register_node(my.flows.play_sound, Stl.PlayEvent)
end node
  • TACT.Modify.EvalArtics reads the current keyswitch state and sets Voice[self].tact.main_artic
  • TACT.Modify.FallbackArtics resolves fallback articulations when the primary is disabled
  • TACT.Sys.Modify.Dynamics handles velocity layer selection
  • TACT.Sys.SetADSR applies per-articulation envelope settings
  • TACT.Modify.TriggerArticVolume applies per-articulation volume from the TACT panel

7. Implement TACT voice fields

TACT uses voice fields to track articulation state through the flow. These are created automatically by the TACT node bundle. Your Spawn and PlayEvent nodes use tact.play_artic_modulable() to create voices for specific articulations:

node MyProduct.Spawn:
    cb NoteOn:
        { TACT sets Voice[self].tact.main_artic after EvalArtics runs }
        { Spawn a voice for the active articulation }
        declare new_vo := tact.play_artic_modulable(self, Voice[self].tact.main_artic, my.flows.play_sound)
        ivls.play(new_vo)
end node

tact.play_artic_modulable() creates a new formal voice, sets the TACT articulation fields, and routes it to the specified flow.

8. Load NKA at init

TACT metadata is loaded from NKAs during the tact.config.ICB() macro execution, which runs at init. Make sure your NKA files are in the expected Resources/data/ directory. The tact.config.init_meta() function copies the loaded data into TACT's internal arrays.

Verify

  1. Play your instrument -- the default articulation should sound
  2. Press keyswitch notes -- the articulation should change and the TACT UI should reflect the active artic
  3. Check that per-articulation volume, ADSR, and offset controls work from the TACT panel
  4. Confirm that round robins cycle correctly per articulation

Further reading

  • TACT module reference for the complete API
  • Guide: TACT for conceptual background on articulation management
  • Stl.PlayEvent module reference for the event playback template