Keymaps¶
The keymap system maps each of the 128 MIDI keys to a flow, a visual type, a color, a display name, and a scheduling latency. It is the bridge between raw MIDI input and the voice pipeline. When a key is pressed, IVLS checks the keymap to find which flow the resulting voice should enter.
IVLS supports up to 16 independent maps. Only one is active at a time. Switching maps updates the Kontakt keyboard display and changes which flows key presses route to — useful for articulation or mode switching that changes the entire keyboard layout.
Why Keymaps Exist¶
Without a keymap layer, flow routing would require hard-coded note ranges inside on note handlers. Keymaps make the keyboard layout declarative and data-driven. You describe what each key means — play range, phrase-assign zone, inactive filler — and the runtime handles routing, display, and per-key scheduling budgets automatically.
Key Concepts¶
The Keymap State¶
Per map and per key, the system tracks:
keymap.categories[map, key] // which category owns this key (-1 = unclaimed)
keymap.key_flows[map, key] // flow index for note-on events
keymap.key_latency_us[map, key] // scheduling latency in microseconds (default 1)
keymap.key_types[map, key] // NI_KEY_TYPE_xxx constant
keymap.key_colors[map, key] // KEY_COLOR_xxx constant
keymap.key_names[map, key] // display name stringKeys are owned by categories — named tokens declared by the product. Only the owning category can update or dismiss a key. If two systems try to claim the same key, the second request logs a KeymapFailure error and the existing mapping is preserved.
Declaring Keymaps in cb Keymaps¶
cb Keymaps runs inside keymap.reboot(), which is called on every Reload. Keymaps rebuild on every preset load, so they always reflect current state:
node MyProduct.Keymap:
cb Keymaps:
define KEYMAPS += mf_main_keymap
for keymap.i := 0 to 127
keymap.request(mf_main_keymap, 0, keymap.i,
mf.play_flow, NI_KEY_TYPE_DEFAULT, KEY_COLOR_BLUE, "")
end for
end nodedefine KEYMAPS += registers the category token. keymap.request claims a key with the given flow, type, color, and display name.
Multi-Zone Setup¶
Real instruments divide the keyboard into zones. The inactive-fill pattern — check categories[0, key] = -1 and fill remaining keys — is standard practice:
// Phrase-assign range: green keys → select flow
for keymap.i := phr.assign_min to phr.assign_max
keymap.request(phr.main, 0, keymap.i,
phr.select_flow, NI_KEY_TYPE_DEFAULT, KEY_COLOR_GREEN, "")
end for
// Play range: cyan keys → play flow
for keymap.i := phr.play_min to phr.play_max
keymap.request(phr.main, 0, keymap.i,
phr.play_flow, NI_KEY_TYPE_DEFAULT, KEY_COLOR_CYAN, "")
end for
// Fill remaining keys as inactive
for keymap.i := 0 to 127
if keymap.categories[0, keymap.i] = -1
keymap.request(phr.main, 0, keymap.i,
phr.no_play_flow, NI_KEY_TYPE_NONE, KEY_COLOR_INACTIVE, "")
end if
end forThe inactive fill ensures every key has a defined state in Kontakt's keyboard display and prevents unintended routing through a -1 flow.
Per-Key Latency and Lookahead¶
Every key has a key_latency_us value (default 1 microsecond). When a key is pressed, IVLS waits this many microseconds before launching the voice into its flow. This is how lookahead scheduling is implemented.
Tokyo Scoring Strings sets a 4 ms latency on all playable keys:
keymap.update_key_latency_us(tokyo, 0, tky.playback.i, 4000)The 4 ms budget gives the lookahead algorithm time to examine the incoming note and prepare legato transitions before the sound actually fires. Keys that do not need pre-roll stay at 1 µs — effectively immediate.
Dynamic Key Updates¶
After the initial cb Keymaps run, individual keys can be updated at runtime. All update functions enforce category ownership:
keymap.update_key_color(category, map_i, key_i, color)
keymap.update_key_type(category, map_i, key_i, type)
keymap.update_key_name(category, map_i, key_i, name)
keymap.update_key_latency_us(category, map_i, key_i, latency_us)
keymap.dismiss(category, map_i, key_i, flow, type, color, name) // release ownership
keymap.dismiss_all_of(category, map_i) // release all keys for categoryBatch Operations¶
keymap.select_map(map_i) // switch active map and push all 128 keys
keymap.push(key_i) // push single key display state
keymap.push_all() // push all 128 keys
keymap.suspend() // pause per-key sync for batch updates
keymap.resume() // resume sync and push all
keymap.reboot() // re-init + re-run cb Keymaps + push allUse keymap.suspend() / keymap.resume() when reconfiguring the entire keyboard at once — for example on preset load.
How Note-On Connects to Keymaps¶
When a MIDI note arrives, IVLS's on note handler:
- Reads
keymap.key_flows[keymap.selected_map, note]to find the target flow - Creates a key parent voice with
note,vel,midi_ch, andvmcset - Calls
ivls.reflow_voice(key_parent_vo, flow)to route it - Waits
keymap.key_latency_us[keymap.selected_map, note]microseconds - Calls
Voice._process(play_vo)— the voice enters the flow at stage 0
If key_flows[selected_map, note] is -1 (unclaimed), IVLS skips voice creation entirely.
Connections to Other Parts of IVLS¶
Keymaps are the entry point for the entire note pipeline. The flows they reference are declared in cb Flows. Per-key latency directly enables the lookahead engine. The cb Keymaps callback pattern parallels cb Flows — both are declarative setup callbacks that run at load time.
Patterns and Caveats¶
- Always fill unclaimed keys with an explicit entry (
NI_KEY_TYPE_NONE,KEY_COLOR_INACTIVE). Leavingcategories[map, key] = -1means the key is silently skipped on note-on, which is sometimes correct but should be deliberate. - Category ownership prevents two systems from fighting over the same key. If a key silently fails to respond, check whether another category claimed it first.
cb Keymapsruns on everyReload, not just on first load. Keymap state is always rebuilt from scratch, so any runtime modifications (viaupdate_key_coloretc.) are transient and will be overwritten on the next reload unless you replicate the change insidecb Keymaps.- Latency values are in microseconds. 1 µs is effectively immediate. 4000 µs is 4 ms. The maximum useful latency is bounded by player tolerance for latency; most lookahead windows are in the 300–1000 ms range but per-key latency only controls the initial delay before the voice enters the flow.
Related¶
- flows — how flows are declared and voices routed through them
- lookahead — lookahead scheduling and per-key latency in depth