Modules & Persistence¶
krach sessions can be captured, serialized, and replayed. The module system turns a live session into a frozen specification (ModuleIr) that can be saved, loaded, shared, and composed.
Capture & instantiate¶
# Build a session
bass = kr.node("bass", bass_fn, gain=0.3)
verb = kr.node("verb", reverb_fn, gain=0.3)
bass >> (verb, 0.4)
bass @ kr.seq("A2", "D3", None, "E2").over(2)
kr.tempo = 128
# Capture the entire state as frozen IR
ir = kr.capture()
# Later: replay on a fresh mixer
kr.load(ir)
capture() returns a ModuleIr — a frozen, serializable snapshot of everything: nodes (with their DspGraph IR), routing, patterns, controls, transport, and mute state.
load(ir) replays it: creates nodes, connects routing, plays patterns, sets controls.
Save & recall (in-memory scenes)¶
kr.save("verse") # capture() + store by name
kr.save("chorus")
kr.recall("verse") # clear + instantiate the saved scene
kr.recall("chorus")
kr.scenes # → ["verse", "chorus"]
JSON serialization¶
import json
# Serialize
ir = kr.capture()
data = ir.to_dict()
json.dump(data, open("session.json", "w"))
# Deserialize
from krach.ir.module import ModuleIr
data = json.load(open("session.json"))
ir = ModuleIr.from_dict(data)
kr.load(ir)
The serialized format embeds DspGraphs inline — the full signal computation IR for each node. When you load and instantiate, the DSP is re-transpiled to FAUST and JIT-compiled. Nodes with identical DspGraphs (same graph_key) share compiled binaries.
Export to Python script¶
Generates a self-contained Python script that recreates the session:
# Generated by kr.export()
import json
from krach.pattern.pattern import Pattern
from krach.pattern.serialize import dict_to_pattern_node
with kr.batch():
kr.node("bass", bass_fn, gain=0.3)
kr.node("verb", reverb_fn, gain=0.3)
kr.connect("bass", "verb", level=0.4)
kr.tempo = 128
# ... patterns, controls, etc.
Tracing proxy¶
For programmatic module construction without starting audio:
proxy = kr.trace()
proxy.node("kick", "faust:kick", gain=0.8)
proxy.node("hat", "faust:hat", gain=0.3)
proxy.send("kick", "verb", level=0.2)
proxy.play("kick", kr.hit() * 4)
proxy.tempo = 128
ir = proxy.build() # → ModuleIr (no audio started)
kr.load(ir) # now play it
The proxy records calls as ModuleIr without connecting to the engine. Useful for building reusable module templates.
@kr.module decorator¶
Define reusable modules as traced functions. The first parameter is a ModuleProxy — it records calls without starting audio:
@kr.module
def drums(m, tempo=128):
m.node("kick", "faust:kick", gain=0.8)
m.node("hat", "faust:hat", gain=0.3)
m.send("kick", "hat", level=0.2)
m.play("kick", kr.hit() * 4)
m.tempo = tempo
m.outputs("kick", "hat")
ir = drums(tempo=140) # → ModuleIr (no audio)
The decorated function returns a frozen ModuleIr. Call it with any extra arguments — the proxy parameter is injected automatically.
ModuleHandle¶
kr.instantiate(ir, prefix) replays a ModuleIr with namespaced nodes and returns a ModuleHandle:
d = kr.instantiate(drums_ir, "drums")
# Operator DSL — delegates to first declared input/output
d >> verb # route output to verb
bass >> d # route bass into input
d @ pattern # play on first input
d["kick/cutoff"] = 1200 # control access
# Properties
d.input # → NodeHandle for first declared input
d.output # → NodeHandle for first declared output
d.nodes # → {"kick": NodeHandle, "hat": NodeHandle}
d.prefix # → "drums"
All node names are prefixed: "kick" becomes "drums/kick". The existing / path addressing works naturally.
kr.scene(name) and kr.load(ir)¶
kr.scene(name) retrieves a saved scene by name (returns ModuleIr):
kr.load(ir) replays a ModuleIr onto the mixer (session replay). It flattens any sub_modules automatically:
Module composition with m.sub()¶
Compose modules by nesting them inside a @kr.module definition:
@kr.module
def kit(m):
m.node("kick", "faust:kick", gain=0.8)
m.node("hat", "faust:hat", gain=0.3)
m.outputs("kick", "hat")
@kr.module
def full_band(m):
m.node("bass", "faust:bass", gain=0.5)
drums = m.sub("drums", kit()) # nest kit as "drums/*"
m.send(drums.output("kick"), "bass", level=0.3)
m.outputs("bass")
ir = full_band()
band = kr.instantiate(ir, "band")
# Nodes: band/bass, band/drums/kick, band/drums/hat
m.sub(prefix, ir) returns a SubModuleRef with .input(name) and .output(name) for validated path strings. Typos are caught at trace time, not instantiation time.
prefix_ir() and flatten() (advanced)¶
For manual composition without the decorator:
from krach.ir.module import prefix_ir, flatten
# Namespace all names in an IR
namespaced = prefix_ir(drums_ir, "drums")
# "kick" → "drums/kick", controls and routes follow
# Resolve sub_modules into flat nodes
flat = flatten(parent_ir)
# Recursively prefixes and merges all sub_module nodes
prefix_ir(ir, prefix) rewrites all node names, routes, patterns, controls, and automations with the prefix. flatten(ir) recursively resolves sub_modules into a single flat IR (parent-wins for transport settings).